5

我想使用 doobie 中的 for-comprehension 在一个事务中运行多个查询。就像是:

def addImage(path:String) : ConnectionIO[Image] = {
  sql"INSERT INTO images(path) VALUES($path)".update.withUniqueGeneratedKeys('id', 'path')
}

def addUser(username: String, imageId: Optional[Int]) : ConnectionIO[User] = {
  sql"INSERT INTO users(username, image_id) VALUES($username, $imageId)".update.withUniqueGeneratedKeys('id', 'username', 'image_id')
}

def createUser(username: String, imagePath: Optional[String]) : Future[User] = {
  val composedIO : ConnectionIO[User] = for {
    optImage <- imagePath.map { p => addImage(p) }
    user <- addUser(username, optImage.map(_.id))
  } yield user

  composedIO.transact(xa).unsafeToFuture
}

我刚开始使用 doobie(和猫),所以我对 FreeMonads 不太熟悉。我一直在尝试不同的解决方案,但为了便于理解,看起来两个块都需要返回 cat.free.Free[doobie.free.connection.ConnectionOp,?]。

如果这是真的,有没有办法将我的 ConnectionIO[Image] (来自 addImage 调用)转换为 cat.free.Free[doobie.free.connection.ConnectionOp,Option[Image]] ?

4

2 回答 2

9

对于您的直接问题,ConnectionIO定义为type ConnectionIO[A] = Free[ConnectionOp, A],即这两种类型是等价的(无需转换)。

您的问题是不同的,如果我们逐步执行代码,可以很容易地看到。为简单起见,我将使用Option您使用Optional的 .

  1. imagePath.map { p => addImage(p) }

    imagePath是一个选项,并map使用一个A => B转换Option[A]Option[B]

    由于addImage返回 a ConnectionIO[Image],我们现在有一个Option[ConnectionIO[Image]],即这是一个Option程序,而不是一个ConnectionIO程序。

    我们可以ConnectionIO[Option[Image]]通过替换map来返回 a traverse,它使用Traverse类型类,有关其工作原理的一些详细信息,请参见https://typelevel.org/cats/typeclasses/traverse.html。但是一个基本的直觉是 wheremap会给你一个F[G[B]]traverse而不是给你一个G[F[B]]. 从某种意义上说,它的工作方式Future.traverse与标准库类似,但方式更通用。

  2. addUser(username, optImage.map(_.id))

    这里的问题是给定optImagewhich 是 an Option[Image],其id字段是 an Option[Int],结果optImage.map(_.id)是 an Option[Option[Int]],而不是Option[Int]您的方法所期望的。

    解决此问题的一种方法(如果它符合您的要求)是将这部分代码更改为

    addUser(username, optImage.flatMap(_.id))

    flatMap 可以将 anOption与由其值创建的另一个“连接”(如果存在)。

(注意:您需要添加import cats.implicits._以获取 的语法traverse)。

一般来说,这里关于Traverse,flatMap等的一些想法对学习很有用,两本书是“Scala With Cats”(https://underscore.io/books/scala-with-cats/)和“使用 Scala 进行函数式编程”(https://www.manning.com/books/functional-programming-in-scala

doobie 的作者最近也发表了关于“效果”的演讲,这可能有助于提高你对 , 等类型的直觉OptionIOhttps: //www.youtube.com/watch?v=po3wmq4S15A

于 2018-02-23T09:53:20.657 回答
2

如果我的意图正确,您应该使用traverse而不是map

  val composedIO : ConnectionIO[User] = for {
    optImage <- imagePath.traverse { p => addImage(p) }
    user <- addUser(username, optImage.map(_.id))
  } yield user

您可能需要导入cats.instances.option._和/或cats.syntax.traverse._

于 2018-02-23T09:54:09.777 回答