I'm trying to send an email in the same transaction as inserting user into a database with Doobie.
I know that I can lift IO
into ConnectionIO
by using Async[ConnectionIO].liftIO(catsIO)
where catsIO: IO[String]
But in my code I don't operate on IO
, I use F
with constraints, for example F[_]: Async
So then I can replace F
with my own monad for testing.
Is it possible to somehow lift an F[String]
into ConnectionIO[String]
without using IO
type directly?
Here is an answer I found for IO type: Doobie and DB access composition within 1 transaction
Cats has something called FunctionK which is a natural transformation.
I did this:
At the top of the world, where everything is built, you will need this
val liftToConnIO: FunctionK[IO, ConnectionIO] = LiftIO.liftK[ConnectionIO]
In the class needing to transform from F[String] to G[String] (F will be IO, G will be ConnectionIO when you construct everything) you can pass liftToConnIO
and use it to transform F[A]to G[A] where needed.
The class that doesn't wants to abstract over IO and ConnectionIO can be passed the FunctionK to do the lifting:
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 ()
}
(You might need context bounds (Sync?) on F and G)
Yes, you can easily instantiate your F[String]
into ConnectionIO[String]
.
Given a function like:
def foo[F[_]: Async]: F[String] = ...
To instantiate in to ConnectionIO
you can simply do this:
def fooCIO: ConnectionIO[String] = foo[ConnectionIO]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With