typelevel/cats-mtl

Bracket for MTL

iRevive opened this issue · 2 comments

Hi all.

Bracket typeclass, that exists in cats-effect, doesn't work well with an effect that relies on FunctorRaise.
I want to implement a clean-up logic in case of any error (raised either by MonadError or FunctorRaise).

Example:

import cats.data.EitherT
import cats.effect._
import cats.effect.syntax.bracket._
import cats.mtl._
import cats.mtl.implicits._

final case class AppError(reason: String)

class Service[F[_]: Sync: ApplicativeHandle[?[_], AppError]: FunctorRaise[?[_], AppError]] {

  def createActive: F[String] =
    create.bracketCase(uuid => makeActive(uuid)) {
      case (_, ExitCase.Completed)   => Sync[F].unit // There can be an error too
      case (uuid, ExitCase.Error(e)) => destroy(uuid)
      case (uuid, ExitCase.Canceled) => destroy(uuid)
    }

  def makeActive(uuid: String): F[String] = FunctorRaise[F, AppError].raise(AppError("Oops. Something went wrong"))
  def create: F[String]                   = Sync[F].pure("some uuid")
  def destroy(uuid: String): F[Unit]      = Sync[F].unit

}

object Service {

  def main(args: Array[String]): Unit = {
    type Effect[A] = EitherT[IO, AppError, A]
    val service = new Service[Effect]

    println(service.createActive.value.unsafeRunSync()) // Left(AppError("Oops. Something went wrong"))
  }

}

For sure this is the correct behavior of a Bracket typeclass and clean-up logic for a specific error can be managed inside the bracket:

create.bracketCase(uuid => makeActive(uuid).handleError[AppError](e => destroy(uuid) >> e.raise)) { ... }

But should there be an alternative Bracket that manages specific errors raised by FunctorRaise? For example, extended ADT can be used:

sealed trait ExitCase[+E1, +E2]
object ExitCase {
  final case object Completed                  extends ExitCase[Nothing, Nothing]
  final case class Error[E](cause: E)          extends ExitCase[E, Nothing]
  final case class UnhandledError[E](cause: E) extends ExitCase[Nothing, E]
  final case object Canceled                   extends ExitCase[Nothing, Nothing]
}

I'd recommend you to look into Bifunctor IO implementations such as ZIO or cats-bio – they'll behave as expected in this scenario, unlike EitherT

Hi @Kaishh, thanks. Bifunctor solved my problem.