在doobie中为for-comprehension撰写可选查询?

时间:2018-02-23 09:07:32

标签: scala scala-cats free-monad 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(和猫),所以我对FreeMonads并不熟悉。我一直在尝试不同的解决方案但是为了理解工作,看起来两个块都需要返回cats.free.Free [doobie.free.connection.ConnectionOp,?]。

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

2 个答案:

答案 0 :(得分:4)

对于直接问题,ConnectionIO定义为type ConnectionIO[A] = Free[ConnectionOp, A],即两种类型相同(不需要转换)。

您的问题不同,如果我们一步一步地执行代码,就可以轻松看到。为简单起见,我将使用您使用Option的{​​{1}}。

  1. Optional

    imagePath.map { p => addImage(p) }是一个选项,imagePath使用mapA => 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]]的工作方式类似,但更为一般。

  2. Future.traverse

    此处的问题是addUser(username, optImage.map(_.id)) optImage及其Option[Image]字段idOption[Int]的结果为optImage.map(_.id) Option[Option[Int]],而不是您的方法所期望的Option[Int]

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

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

    flatMap可以&#34;加入&#34;一个Option,另一个由其值创建(如果存在)。

  3. (注意:您需要添加import cats.implicits._才能获得traverse的语法。

    一般来说,这里关于TraverseflatMap等的一些想法对于学习是有用的,并且这两本书是#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;,这可能有助于提高您对OptionIO等类型的直觉。{ {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._