我想在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并不熟悉。我一直在尝试不同的解决方案但是为了理解工作,看起来两个块都需要返回cats.free.Free [doobie.free.connection.ConnectionOp,?]。
如果这是真的,有没有办法将我的ConnectionIO [Image](从addImage调用)转换为cats.free.Free [doobie.free.connection.ConnectionOp,Option [Image]]?
答案 0 :(得分:4)
对于直接问题,ConnectionIO
定义为type ConnectionIO[A] = Free[ConnectionOp, A]
,即两种类型相同(不需要转换)。
您的问题不同,如果我们一步一步地执行代码,就可以轻松看到。为简单起见,我将使用您使用Option
的{{1}}。
Optional
:
imagePath.map { p => addImage(p) }
是一个选项,imagePath
使用map
将A => B
转换为Option[A]
。
由于Option[B]
返回addImage
,我们现在有一个ConnectionIO[Image]
,即这是一个Option[ConnectionIO[Image]]
程序,而不是Option
程序。
我们可以通过将ConnectionIO
替换为使用ConnectionIO[Option[Image]]
类型类的map
来返回traverse
,有关其工作原理的详细信息,请参阅https://typelevel.org/cats/typeclasses/traverse.html 。但基本的直觉是,Traverse
会给你一个map
,而F[G[B]]
会给你一个traverse
。从某种意义上说,它与标准库中的G[F[B]]
的工作方式类似,但更为一般。
Future.traverse
此处的问题是addUser(username, optImage.map(_.id))
optImage
及其Option[Image]
字段id
,Option[Int]
的结果为optImage.map(_.id)
Option[Option[Int]]
,而不是您的方法所期望的Option[Int]
。
解决此问题的一种方法(如果符合您的要求)是将此部分代码更改为
addUser(username, optImage.flatMap(_.id))
flatMap可以&#34;加入&#34;一个Option
,另一个由其值创建(如果存在)。
(注意:您需要添加import cats.implicits._
才能获得traverse
的语法。
一般来说,这里关于Traverse
,flatMap
等的一些想法对于学习是有用的,并且这两本书是#34; Scala With Cats&#34; (https://underscore.io/books/scala-with-cats/)和&#34;使用Scala进行功能编程&#34; (https://www.manning.com/books/functional-programming-in-scala)
doobie的作者最近还讨论了&#34;效果&#34;,这可能有助于提高您对Option
,IO
等类型的直觉。{ {3}}
答案 1 :(得分: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._