【问题标题】:Composing multiple different monad types in a for-comprehension在理解中组合多种不同的单子类型
【发布时间】:2018-10-20 17:54:21
【问题描述】:

上一个标题:在理解中编写 DBIO

我不明白,为什么下面的代码甚至无法编译。

我想做什么/背景

对于电影售票条目列表中的每个条目,如果在我的数据库中找到该电影,则插入它。

问题似乎是,我不能在理解中使用 DBIO。这是为什么?是因为我在同一个理解中使用了不同类型的单子吗?

val movieTicketSaleNumbers: List[MovieTicketSale] = cinemaApi.allMovieTicketSales

val insertMetricActions: List[DBIO[UUID]] = for {
  movieTicketSaleNumber: MovieTicketSale <- movieTicketSaleNumbers
  isInDatabaseAction: DBIO[Option[Movie]] = moviesDb.findOneExact(movieTicketSaleNumber.movie.id)
  optionalMovie: Option[Movie] <- isInDatabaseAction
  movieInDatabase: Movie <- optionalMovie
  insertMovieNumbersInDatabaseAction: DBIO[UUID] = insertMovieTicketSale(movieTicketSaleNumber, movieInDatabase)
  movieNumberDbId: UUID <- insertMovieNumbersInDatabaseAction
} yield movieNumberDbId

编译器输出:

[error]  found   : slick.dbio.DBIOAction[java.util.UUID,slick.dbio.NoStream,slick.dbio.Effect.All]
[error]  required: Option[?]
[error]         movieNumberDbId: UUID <- insertMovieNumbersInDatabaseAction
[error]                        ^
[error] [PROJECTPATHPLACEHOLDER]: type mismatch;
[error]  found   : Option[Nothing]
[error]  required: slick.dbio.DBIOAction[?,?,?]
[error]         movieInDatabase: Movie <- optionalMovie
[error]                              ^
[error] [PROJECTPATHPLACEHOLDER]: type mismatch;
[error]  found   : slick.dbio.DBIOAction[Nothing,Nothing,slick.dbio.Effect.All with slick.dbio.Effect]
[error]  required: scala.collection.GenTraversableOnce[?]
[error]         optionalMovie: Option[Movie] <- isInDatabaseAction
[error]                                    ^
[error] three errors found
[error] (Compile / compileIncremental) Compilation failed

【问题讨论】:

    标签: scala slick


    【解决方案1】:

    是的,这是因为您在 for 理解中使用了不同类型的 monad。

    想想不加糖的版本。 Scala 的理解可以归结为一系列 mapflatMap 调用。 flatMap 的类型基本上是这样定义的:

    def flatMap[F[_], A, B](item: F[A])(fn: A => F[B]): F[B]
    

    请注意,虽然内部类型发生了变化,但包装类型始终是相同的 F 类型。在这里,您将 DBIO 效果类型与相同的 Option 混合以进行理解——这违反了 flatMap 的定义。

    在你的情况下,如果你想把整个事情保持在 for 的理解中,你可以试试 Cats 的 OptionT monad 转换器:https://typelevel.org/cats/datatypes/optiont.htmlOptionT 本质上提供了一个包装器,允许您将 monadic 值 F[Option[_]] 本身视为一个 monadic 值。请注意,您还有一个 List,它是第三种单子类型。所以你的计算最终可能看起来像:

    import cats._
    import cats.data._
    import cats.implicits._
    
    val movieTicketSaleNumbers: List[MovieTicketSale] = cinemaApi.allMovieTicketSales
    
    def insertTicket(sale: MovieTicketSale): OptionT[DBIO, UUID] = 
      for {
        movie <- OptionT(moviesDb.findOneExact(sale.movie.id))
        movieNumberDbId <- OptionT.liftF(insertMovieTicketSale(sale, movie))
      } yield movieNumberDbId
    
    val insertMetricActions: List[DBIO[Option[UUID]]] = movieTicketSaleNumbers.map(insertTicket(_).value)
    

    这将为您提供包含插入的可选 UUID 的效果列表。

    不过,您不需要 Cats 来执行此操作。你可以在原版 Scala 中做你想做的事,虽然它有点笨拙:

    val movieTicketSaleNumbers: List[MovieTicketSale] = cinemaApi.allMovieTicketSales
    
    def insertTicket(sale: MovieTicketSale): DBIO[Option[UUID]] = 
      for {
        movie <- moviesDb.findOneExact(sale.movie.id)
        movieNumberDbId <- movie.map(insertMovieTicketSale(sale, _).map(Option(_))).getOrElse(DBIO.successful(None))
      } yield movieNumberDbId
    
    
    val insertMetricActions: List[DBIO[Option[UUID]]] = movieTicketSaleNumbers.map(insertTicket(_))
    

    可能有一种更优雅的方式来表达这一点,尤其是将Option[DBIO[UUID]] 转换为DBIO[Option[UUID]]

    希望有帮助!

    【讨论】:

    • 非常感谢@lawrence。我已经计划接触猫一段时间了,所以这很棒。不过,出于学习目的,您介意分享一下香草 Scala 的外观吗?
    • 嗨@SamBokai,我已经更新了我的答案,包括了一种香草scala的做法。它不那么优雅,我敢肯定有更好的表达方式,但希望它能带来一些启发。
    • 它是如此明显,我已经看到了...非常感谢。猫看起来很棒
    猜你喜欢
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多