在 doobie 中为 for-comprehension 编写可选查询?
Compose optional queries for for-comprehension in doobie?
我想 运行 在一个事务中使用 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(和 cats),所以我对 FreeMonads 不是很熟悉。我一直在尝试不同的解决方案,但为了理解工作,看起来两个块都需要 return a cats.free.Free[doobie.free.connection.ConnectionOp,?].
如果这是真的,有没有办法将我的 ConnectionIO[Image](从 addImage 调用)转换为 cats.free.Free[doobie.free.connection.ConnectionOp,Option[Image]]?
对于你直接的问题,ConnectionIO
定义为type ConnectionIO[A] = Free[ConnectionOp, A]
,即两种类型是等价的(不需要转换)。
你的问题不一样,我们一步步看代码就很容易看出来。为简单起见,我将在您使用 Optional
.
的地方使用 Option
imagePath.map { p => addImage(p) }
:
imagePath
是一个 Option,map
使用 A => B
将 Option[A]
转换为 Option[B]
。
由于 addImage
return 是 ConnectionIO[Image]
,我们现在有一个 Option[ConnectionIO[Image]]
,即这是一个 Option
程序,而不是 ConnectionIO
程序。
我们可以通过将 map
替换为 traverse
来代替 return 一个 ConnectionIO[Option[Image]]
,它使用 Traverse
类型类,参见 https://typelevel.org/cats/typeclasses/traverse.html有关其工作原理的一些详细信息。但是一个基本的直觉是 map
会给你一个 F[G[B]]
,traverse
却给你一个 G[F[B]]
。从某种意义上说,它的工作方式类似于标准库中的 Future.traverse
,但方式更通用。
addUser(username, optImage.map(_.id))
这里的问题是给定 optImage
是一个 Option[Image]
,它的 id
字段是一个 Option[Int]
,[=39= 的结果] 是 Option[Option[Int]]
,而不是您的方法所期望的 Option[Int]
。
解决此问题的一种方法(如果符合您的要求)是将这部分代码更改为
addUser(username, optImage.flatMap(_.id))
flatMap 可以 "join" 一个 Option
和另一个由它的值创建的(如果它存在的话)。
(注意:您需要添加 import cats.implicits._
以获得 traverse
的语法)。
总的来说,这里关于Traverse
、flatMap
等的一些想法,对学习很有帮助,有两本书是"Scala With Cats"(https://underscore.io/books/scala-with-cats/) and "Functional Programming with Scala" (https://www.manning.com/books/functional-programming-in-scala)
doobie 的作者最近也发表了一篇关于 "effects" 的演讲,这可能有助于提高您对 Option
、IO
等类型的直觉:https://www.youtube.com/watch?v=po3wmq4S15A
如果我理解你的意图,你应该使用 traverse
而不是 map
:
val composedIO : ConnectionIO[User] = for {
optImage <- imagePath.traverse { p => addImage(p) }
user <- addUser(username, optImage.map(_.id))
} yield user
您可能需要导入 cats.instances.option._
and/or cats.syntax.traverse._
我想 运行 在一个事务中使用 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(和 cats),所以我对 FreeMonads 不是很熟悉。我一直在尝试不同的解决方案,但为了理解工作,看起来两个块都需要 return a cats.free.Free[doobie.free.connection.ConnectionOp,?].
如果这是真的,有没有办法将我的 ConnectionIO[Image](从 addImage 调用)转换为 cats.free.Free[doobie.free.connection.ConnectionOp,Option[Image]]?
对于你直接的问题,ConnectionIO
定义为type ConnectionIO[A] = Free[ConnectionOp, A]
,即两种类型是等价的(不需要转换)。
你的问题不一样,我们一步步看代码就很容易看出来。为简单起见,我将在您使用 Optional
.
Option
imagePath.map { p => addImage(p) }
:imagePath
是一个 Option,map
使用A => B
将Option[A]
转换为Option[B]
。由于
addImage
return 是ConnectionIO[Image]
,我们现在有一个Option[ConnectionIO[Image]]
,即这是一个Option
程序,而不是ConnectionIO
程序。我们可以通过将
map
替换为traverse
来代替 return 一个ConnectionIO[Option[Image]]
,它使用Traverse
类型类,参见 https://typelevel.org/cats/typeclasses/traverse.html有关其工作原理的一些详细信息。但是一个基本的直觉是map
会给你一个F[G[B]]
,traverse
却给你一个G[F[B]]
。从某种意义上说,它的工作方式类似于标准库中的Future.traverse
,但方式更通用。addUser(username, optImage.map(_.id))
这里的问题是给定
optImage
是一个Option[Image]
,它的id
字段是一个Option[Int]
,[=39= 的结果] 是Option[Option[Int]]
,而不是您的方法所期望的Option[Int]
。解决此问题的一种方法(如果符合您的要求)是将这部分代码更改为
addUser(username, optImage.flatMap(_.id))
flatMap 可以 "join" 一个
Option
和另一个由它的值创建的(如果它存在的话)。
(注意:您需要添加 import cats.implicits._
以获得 traverse
的语法)。
总的来说,这里关于Traverse
、flatMap
等的一些想法,对学习很有帮助,有两本书是"Scala With Cats"(https://underscore.io/books/scala-with-cats/) and "Functional Programming with Scala" (https://www.manning.com/books/functional-programming-in-scala)
doobie 的作者最近也发表了一篇关于 "effects" 的演讲,这可能有助于提高您对 Option
、IO
等类型的直觉:https://www.youtube.com/watch?v=po3wmq4S15A
如果我理解你的意图,你应该使用 traverse
而不是 map
:
val composedIO : ConnectionIO[User] = for {
optImage <- imagePath.traverse { p => addImage(p) }
user <- addUser(username, optImage.map(_.id))
} yield user
您可能需要导入 cats.instances.option._
and/or cats.syntax.traverse._