typelevel / cats-mtl

cats transformer type classes.
https://typelevel.org/cats-mtl/
Other
307 stars 62 forks source link

New typeclass to run reader-like effects #287

Open FunFunFine opened 3 years ago

FunFunFine commented 3 years ago

In short one may need to mimic creating and running ReaderT subprogram using Tagless Final approach.

Consider this endpoint in some app without TF:

class Endpoints(fooService: FooService, barService: BarService, ...){
  def endpoints = HttpRoutes.of[IO] {
    case ROOT -> "handle" => for {
        body <- req.as[Body]
        trace = genTrace
        logContext = buildCtx(body)
        auth = req.authInfo
        result <- handle.run(auth, trace, logContext)
        response <- Ok(result)
      } yield response
  }
  def handle(body: Body): ReaderT[IO, (Auth, Trace, LogCtx), Result] = ???
  //this method calls multiple other services and methods which also return `ReaderT[IO, ...]`. 
  //some of them are using context from reader to provide contextual logging or to avoid passing authorization as method parameters
}

And AFAIK there is no way to represent this behavior in tagless final code right now.

So here comes my proposal: cats-mtl should have some typeclass which can represent this, let's call it Run:

trait Run[F[_], G[_], R] {
  def run(fa: G[A])(r: R): F[A]
}

it should provide context of type R to computation ga of type G[A] to evaluate it to another computation of typeF[A].

So now we can write the endpoint like that:

class Endpoints[Main[_], Contextual[_]](fooService: FooService[Contextual], barService: BarService[Contextual], ...)(implicit run: Run[Main, Contextual, (Auth, Trace, LogCtx)]) {

  def endpoints = HttpRoutes.of[Main] {
    case ROOT -> "handle" => for {
        body <- req.as[Body]
        trace = genTrace
        logContext = buildCtx(body)
        auth = req.authInfo
        result <- run.run(handle(body))((auth, trace, logContext)) //  Main[Result]
        response <- Ok(result)
      } yield response
  }
  def handle(body: Body): Contextual[Result] = ???
  //this method calls multiple services and methods which look like FooService[Contextual]`. 
}

//and creation of endpoints:
type Context = (Auth, Trace, LogCtx)
val endpoints = Endpoint[IO, ReaderT[IO, Context, *], Context](foo, bar) // or could be zio.Task and zio.RIO[Context, *]

So what do you think about this proposal? I could try to implement this if it seems like a good idea.

rossabaker commented 1 year ago

Apologies, I've never been a core maintainer, but I'm cleaning up.

Are there any laws Run might have, either for itself, or in relation to other classes?