Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New typeclass to run reader-like effects #287

Open
FunFunFine opened this issue Nov 30, 2020 · 1 comment
Open

New typeclass to run reader-like effects #287

FunFunFine opened this issue Nov 30, 2020 · 1 comment

Comments

@FunFunFine
Copy link

FunFunFine commented Nov 30, 2020

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
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants