cats-mtl
cats-mtl copied to clipboard
FunctorListen doesn't understand nested Monad stack
I'm trying to stack a WriterT and EitherT together - ultimately something like IO[Writer[Log, Either[Throwable, A]]]. The idea is that even in the event of an error the log up to that point should be maintained.
I have created a POC which uses a transformer stack of EitherT[WriterT[IO, Log, *], Throwable, *] and when I materialise the program it works as expected. The logs are present even in the event of an error. However FunctorListen seems to short circuit in the event of an error. Here is the code itself:
object POC extends IOApp {
import cats.instances.vector._
import cats.mtl.implicits._
import cats.syntax.flatMap._
import cats.syntax.functor._
type Log = Vector[String]
override def run(args: List[String]): IO[ExitCode] = {
val name = getName[EitherT[WriterT[IO, Log, *], Throwable, *]]
// this works even with a raised error so the stack itself is working
name.value.written.flatTap(logs => IO(println(logs))).as(ExitCode.Success)
// this won't work
printLog(name).value.run.as(ExitCode.Success)
}
def printLog[F[_], A](fa: F[A])(implicit F: Sync[F], FL: FunctorListen[F, Log]): F[A] = {
// this is never called when an error is raised
fa.listen.flatMap { case (a, logs) => F.delay(println(logs)).as(a) }
}
def getName[F[_]](implicit F: Sync[F], FT: FunctorTell[F, Log], FR: FunctorRaise[F, Throwable]): F[String] = for {
_ <- FT.tell(Vector("getting name ..."))
a <- F.delay("bob")
// Exception here means FunctorListen cant find any logs
_ <- FR.raise(new Exception("boom!")): F[Unit]
} yield a
}
I'm not sure if this is a problem with FunctorListen or FunctorRaise. It could be that FunctorRaise is raising an error at the IO level not the Either/EitherT