Relative monads in Scala

Last modified on February 21, 2018 by Alex


trait RelativeMonad[I[_], F[_]] {
  def pure[A](ja: I[A]): F[A]

  def flatMap[A, B](ma: F[A])(f: I[A] => F[B]): F[B]

  def tailRecM[A, B](a: I[A])(f: I[A] => F[Either[A, B]]): F[B]
}
import cats.MonadError

implicit def fromMonadError[E, F[_]](F: MonadError[F, E]): RelativeMonad[Either[E, ?], F] =
  new RelativeMonad[Either[E, ?], F] {
    def pure[A](ja: Either[E, A]): F[A] = ja match {
      case Left(e) => F.raiseError[A](e)
      case Right(a) => F.pure[A](a)
    }

    def flatMap[A, B](ma: F[A])(f: Either[E, A] => F[B]): F[B] =
      F.flatMap(F.attempt(ma))(f)

    def tailRecM[A, B](a: Either[E, A])(f: Either[E, A] => F[Either[A, B]]): F[B] =
      a match {
        case Left(e) => F.raiseError(e)
        case Right(a) => F.tailRecM(a)(a => f(Right(a)))
      }
  }

implicit def toMonadError[E, F[_]](F: RelativeMonad[Either[E, ?], F]): MonadError[F, E] =
  new MonadError[F, E] {
    def pure[A](x: A): F[A] =
      F.pure(Right(x))

    def raiseError[A](e: E): F[A] =
      F.pure(Left(e))

    def flatMap[A, B](fa: F[A])(f: (A) => F[B]): F[B] = F.flatMap(fa) {
      case Right(x) => f(x)
      case Left(e) => F.pure(Left(e))
    }

    def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = F.flatMap(fa) {
      case Right(x) => F.pure(Right(x))
      case Left(e) => f(e)
    }

    def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] =
      F.tailRecM(Right(a)) {
        case Left(e) => raiseError(e)
        case Right(a) => f(a)
      }
  }