全面理解多种不同的monad类型

时间:2018-10-19 18:11:36

标签: scala slick

  

上一个标题:以全面理解的方式组成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

1 个答案:

答案 0 :(得分:3)

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

考虑未加糖的版本。用于理解的Scala归结为一系列mapflatMap的调用。 flatMap的类型基本上是这样定义的:

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

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

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

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]]

希望有帮助!