Bracket for MTL
iRevive opened this issue · 2 comments
iRevive commented
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]
}
neko-kai commented