A while back I submitted the TheseT pull-request (
https://github.com/scalaz/scalaz/pull/1162). I supplied a MonadCatchIO
instance in the PR. Similarly I have submitted a PR including a
MonadCatchIO for EitherT (https://github.com/scalaz/scalaz/pull/1161: not
merged). I have the latter in my own codebase and it has just bitten me.
What would we expect the following to print?
implicit val M = eitherTMonadCatchIO[IO, String]
val N = EitherT.eitherTMonadError[IO, String]
def surround(a: EitherT[IO, String, Int]) =
MonadCatchIO.bracket_(M.liftIO(IO putStrLn "Before"))(M.liftIO(IO putStrLn "
After"))(a)
scala> surround(M.liftIO( IO.putStrLn("During") >| 1 )).run.unsafePerformIO
Before
During
After
res1: scalaz.\/[String,Int] = \/-(1)
So far, so good
scala> surround(N raiseError "Bah").run.unsafePerformIO
Before
res0: scalaz.\/[String,Int] = -\/(Bah)
Oh dear. The issue is that MonadCatchIO only brackets throwables. I can
display a similar issue with TheseT:
scala> implicit val M = MonadCatchIO[({type M[A] = TheseT[IO, String,
A]})#M]
M: scalaz.effect.MonadCatchIO[[A]scalaz.TheseT[scalaz.effect.IO,String,A]]
= scalaz.effect.MonadCatchIO$$anon$2@7d2d5625
scala> def surround(a: TheseT[IO, String, Int]) =
MonadCatchIO.bracket_(M.liftIO(IO putStrLn "Before"))(M.liftIO(IO putStrLn
"After"))(a)
surround: (a: scalaz.TheseT[scalaz.effect.IO,String,Int])scalaz.TheseT[
scalaz.effect.IO,String,Int]
scala> surround(TheseT(IO(\&/.That(1)))).run.unsafePerformIO
Before
After
res2: scalaz.\&/[String,Int] = That(1)
scala> surround(TheseT(IO(\&/.Both("bah", 1)))).run.unsafePerformIO
Before
After
res3: scalaz.\&/[String,Int] = Both(bah,1)
scala> surround(TheseT(IO(\&/.This("bah")))).run.unsafePerformIO
Before
res4: scalaz.\&/[String,Int] = This(bah)
You could argue that this usage is correct as the "failure" is not
resultant from a Throwable inside the stack. But this is of little use if
you are attempting to use *bracket *to acquire and release resources in
that stack (i.e. it doesn't work).
I have fixes for both of these - that is, I have MonadCatchIO instances for
both EitherT and TheseT which cause a handler to be invoked on the lhs (see
below). It would be my opinion that the "pragmatic" course of action would
be to use my new instances over the existing ones. I'm interested in
opinions that differ, particularly if strongly held!
Chris
implicit def theseTMonadCatchIO[M[_]: MonadCatchIO, E: Semigroup] =
new MonadCatchIO[({type l[a]=TheseT[M, E, a]})#l] {
val TheseTMonadIO = MonadIO.theseTMonadIO[M, E]
val M = MonadCatchIO[M]
override def except[A](ma: TheseT[M, E, A])(handler: Throwable =>
TheseT[M, E, A]) = TheseT[M, E, A] {
case class ControlThrowableE(e: E) extends Throwable with
scala.util.control.NoStackTrace
MonadCatchIO.catchSomeLeft[M, E \&/ A, E]( M.except(M.map(ma.run)({
case \&/.This(e) => throw ControlThrowableE(e)
case x => x
}))(handler andThen (_.run)) ) {
case ControlThrowableE(e) => Some(e)
case _ => None
} map {
case -\/(e) => \&/.This(e)
case \/-(t) => t
}
}
override def point[A](a: => A) = TheseTMonadIO.point(a)
override def bind[A, B](fa: TheseT[M, E, A])(f: (A) => TheseT[M, E,
B]) = TheseTMonadIO.bind(fa)(f)
override def liftIO[A](ioa: IO[A]) = TheseTMonadIO.liftIO(ioa)
}
implicit def eitherTMonadCatchIO[M[_]: MonadCatchIO, E] = new
MonadCatchIO[({type l[a]=EitherT[M, E, a]})#l] {
val EitherTMonadIO = MonadIO.eitherTMonadIO[M, E]
override def except[A](ma: EitherT[M, E, A])(handler: Throwable =>
EitherT[M, E, A]) = EitherT[M, E, A] {
case class ControlThrowableE(e: E) extends Throwable with
scala.util.control.NoStackTrace
val M = MonadCatchIO[M]
MonadCatchIO.catchSomeLeft[M, E \/ A, E](
M.except(M.map(ma.run)(_.valueOr(e => throw
ControlThrowableE(e))).map(\/.right[E, A]))(handler andThen (_.run)) )
{
case ControlThrowableE(e) => Some(e)
case _ => None
} map (_.join)
}
override def point[A](a: => A)
= EitherTMonadIO.point(a)
override def bind[A, B](fa: EitherT[M, E, A])(f: A => EitherT[M, E,
B]) = EitherTMonadIO.bind(fa)(f)
override def liftIO[A](ioa: IO[A])
= EitherTMonadIO.liftIO(ioa)
}
--
You received this message because you are subscribed to the Google Groups
"scalaz" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/scalaz.
For more options, visit https://groups.google.com/d/optout.