Search code examples
scalascala-catsmonad-transformers

cats: mapping nested either of option value


I have the following function:

def function(i: Int): IO[Either[String, Option[Int]]] = ???

I want a function of the form:

def foo(either: Either[String, Option[Int]]): IO[Either[String, Option[Int]]]

and I want it to have the following behavior:

def foo1(either: Either[String, Option[Int]])
: IO[Either[String, Option[Int]]] = either match {
  case Right(Some(i)) => bar(i)
  case Right(None) => IO.pure(None.asRight)
  case Left(s) => IO.pure(s.asLeft)
}

I want to do it less explicitly, so I tried EitherT:

def foo2(either: Either[String, Option[Int]]): 
  IO[Either[String, Option[Int]]] = {
    val eitherT = for {
      maybe <- EitherT.fromEither[IO](either)
      int <- EitherT.fromOption(maybe, "???")
      x <- EitherT(bar(int))
    } yield x

  eitherT.value
}

but this means that Right(None) will be mapped to IO(Left("???")) which is not what I want.

  • is there an alternative formulation with EitherT without a match expression that is equivalent to the foo1 implementation?

  • more importantly, how would an implementation that uses map/traverse/biTraverse/etc. (and doesn't match on any of option/eithers) look like?

p.s. The intention here is to define a "map" function for the following type:

trait Lookup[F[_], K, A] {
  def get(key: K): F[Either[FormatError, Option[A]]]
}

Solution

  • without match

    import cats._
    import cats.data._
    import cats.implicits._
    
    def bar[F[_] : Applicative](i: Int): F[Either[String, Option[Int]]] =
      (i + 1).some.asRight[String].pure[F]
    
    def foo[F[_] : Applicative : Monad : FlatMap](either: Either[String, Option[Int]]): F[Either[String, Option[Int]]] =
      OptionT(EitherT(either.pure[F])).flatMap { i =>
        OptionT(EitherT(bar[F](i)))
      }.value.value
    
    foo[Id](1.some.asRight)
    //res0: cats.Id[Either[String,Option[Int]]] = Right(Some(2))