我正在尝试在使用 Doobie 将用户插入数据库的同一事务中发送电子邮件。
我知道我可以使用
IO
将 ConnectionIO
提升到 Async[ConnectionIO].liftIO(catsIO)
,其中 catsIO: IO[String]
IO
进行操作,我使用带有约束的 F
,例如 F[_]: Async
那么我就可以用我自己的 monad 替换 F
来进行测试。
是否可以在不直接使用
F[String]
类型的情况下以某种方式将 ConnectionIO[String]
提升为 IO
?
这是我为 IO 类型找到的答案:Doobie 和 DB 访问组合在 1 个事务中
现在已被更好的解决方案取代此处。
Cats 有一种叫做 FunctionK 的东西,这是一种自然的变换。
我这样做了:
在世界之巅,一切都已建成,你将需要这个
val liftToConnIO: FunctionK[IO, ConnectionIO] = LiftIO.liftK[ConnectionIO]
在需要从 F[String] 转换为 G[String] 的类中(构建所有内容时,F 将是 IO,G 将是 ConnectionIO),您可以传递
liftToConnIO
并使用它将 F[A] 转换为 G[ A] 需要的地方。
不想抽象 IO 和 ConnectionIO 的类可以通过 FunctionK 来完成提升:
class Stuff[F[_], G[_]](emailer: Emailer[F], store: Store[G], liftToG: FunctionK[F, G]) {
def sendEmail: G[Unit] =
for {
_ <- doDatabaseThingsReturnStuffInG
_ <- liftToG(emailer.sendEmail)
_ <- doMoreDatabaseThingsReturnStuffInG
} yield ()
}
(您可能需要 F 和 G 上的上下文边界(同步?))
钱宁答案的变体,
class Stuff[F[_] : Effect, G[_] : LiftIO](emailer: Emailer[F], store: Store[G]) {
def sendEmail: G[Unit] =
for {
_ <- doDatabaseThingsReturnStuffInG
_ <- emailer.sendEmail.toIO.to[G]
_ <- doMoreDatabaseThingsReturnStuffInG
} yield ()
}
Effect[F]
支持通过 F[A]
将 IO[A]
转换为 toIO
,LiftIO[G]
支持通过 IO[A]
将 G[A]
转换为 to[G]
。
是的,您可以轻松地将
F[String]
实例化为 ConnectionIO[String]
。
给定一个如下函数:
def foo[F[_]: Async]: F[String] = ...
要实例化到
ConnectionIO
,您可以简单地执行以下操作:
def fooCIO: ConnectionIO[String] = foo[ConnectionIO]
自 Doobie 1.x(带有猫效应 3)以来,
LiftIO
的 ConnectionIO
实例不再存在(为什么)
相反,您想使用
WeakAsync.liftK
,如 Doobie - 将任意效果提升到 ConnectionIO CE3 中提到的