Skip to content

MonadError with Liskov #2443

@marcin-rzeznicki

Description

@marcin-rzeznicki

I think it is possible to derive an instance of MonadError[M, E1] from MonadError[M, E] if we can prove that E1<~< E. I tried to implement it and came up with:

implicit def liskovMonadError[M[_], E1, E](implicit ME: MonadError[M, E],
                                                     liskov: Liskov[E1, E],
                                                     E1: ClassTag[E1]): MonadError[M, E1] =
    new MonadError[M, E1]{
  override def raiseError[A](e1: E1) = ME.raiseError(liskov.coerce(e1))

  override def handleErrorWith[A](fa: M[A])(f: E1 => M[A]) = ME.recoverWith(fa) {
    case e1: E1 => f(e1)
  }
  override def pure[A](x: A)                                 = ME.pure(x)
  override def flatMap[A, B](fa: M[A])(f: A => M[B])         = ME.flatMap(fa)(f)
  override def tailRecM[A, B](a: A)(f: A => M[Either[A, B]]) = ME.tailRecM(a)(f)
}

The "shady" part is handleErrorWith where we need to check at runtime whether we have the right type (hence the ClassTag). I thought it would not work, but it seems to pass all the MonadError laws:

sealed trait Error
case class SubError1(s: String) extends Error
case class SubError2(s: String) extends Error

type F[A] = Either[Error, A]

implicit val ArbitraryError: Arbitrary[Error] = Arbitrary(
  Arbitrary.arbitrary[String].flatMap(s => Gen.oneOf(SubError1(s), SubError2(s))))
implicit val ArbitrarySubError1: Arbitrary[SubError1] = Arbitrary(
  Arbitrary.arbitrary[String].map(s => SubError1(s)))
implicit val CogenSubError1: Cogen[SubError1] = Cogen[String].contramap(_.s)
implicit val EqError: Eq[Error]               = Eq.fromUniversalEquals[Error]
implicit val EqSubError1: Eq[SubError1]       = Eq.fromUniversalEquals[SubError1]

checkAll("Liskov",
         MonadErrorTests[F, SubError1](liskovMonadError)
           .monadError[Int, String, Int])

Conversely, ApplicativeError might have some kind of def widenError[E1](implicit ev: Liskov[E, E1]): ApplicativeError[F, E1] .

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions