Doobie - lifting arbitrary effect into ConnectionIO
Asked Answered
A

4

6

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

Age answered 9/1, 2020 at 4:22 Comment(0)
N
9

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)

Nematic answered 24/4, 2020 at 10:38 Comment(1)
Note that starting at Doobie 1.0.0, things have changed. See: #71212784Altercate
B
4

A variation on Channing's answer,

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] supports taking an F[A] to an IO[A] via toIO, and LiftIO[G] supports taking an IO[A] to a G[A] via to[G].

Brazier answered 26/4, 2020 at 17:38 Comment(0)
H
3

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]
Hoebart answered 13/1, 2020 at 18:16 Comment(0)
N
1

Since Doobie 1.x (with cats-effect 3), the LiftIO instance for ConnectionIO no longer exists (why)

Instead you want to use WeakAsync.liftK as mentioned in Doobie - lifting arbitrary effect into ConnectionIO CE3

Nariko answered 31/8, 2022 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.