0

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]]]
}
Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
Maths noob
  • 1,684
  • 20
  • 42

3 Answers3

1
import cats.Applicative
import cats.syntax.applicative._

def bar[F[_]](i: Int): F[Either[String, Option[Int]]] = ???

def foo[F[_] : Applicative](either: Either[String, Option[Int]]): F[Either[String, Option[Int]]] =
  either match {
    case Right(Some(i)) => bar(i)
    case a => a.pure[F]
  }
Dmytro Orlov
  • 186
  • 4
  • Thanks Dmytro, but That's essentially a glorified version of `foo1`. it is still using a match statement which I want to know if I can avoid. – Maths noob Aug 29 '19 at 09:10
1

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))
Dmytro Orlov
  • 186
  • 4
  • Do you use specific compiler flags to run this? Running it in a scala worksheet gives me: no type parameters for method apply: (value: F[Option[A]])cats.data.OptionT[F,A] in object OptionT exist so that it can be applied to arguments (cats.data.EitherT[F,String,Option[Int]]) --- because --- argument expression's type is not compatible with formal parameter type; found : cats.data.EitherT[F,String,Option[Int]] required: ?F[Option[?A]] OptionT( on line 5. – Maths noob Aug 31 '19 at 02:22
  • quote from https://typelevel.org/cats : **For Scala 2.12 you should add the following to your build.sbt: scalacOptions += "-Ypartial-unification"** for Scala 2.13 you don't need this. Any particular reason not to upgrade to 2.13? – Dmytro Orlov Sep 02 '19 at 10:46
1

With MonadError we can:

  • eliminate 1 transformer during business logic implementation, so only OptionT is needed in def foo
  • do not make decision upfront how we want to handle errors, so user should choose concrete EitherT:
import cats._
import cats.data._
import cats.implicits._
import monix.eval._

type ErrorHandler[F[_]] = MonadError[F, String]

def bar[F[_] : ErrorHandler : Applicative](i: Int): F[Option[Int]] =
  if (i > 0) (i + 1).some.pure[F] else implicitly[ErrorHandler[F]].raiseError("error")

def foo[F[_] : ErrorHandler : Applicative : FlatMap](option: Option[Int]): F[Option[Int]] =
  OptionT(option.pure[F]).flatMap { i =>
    OptionT(bar[F](i))
  }.value

type Effect[A] = EitherT[Task, String, A]

import monix.execution.Scheduler.Implicits.global

foo[Effect](1.some).value.runSyncUnsafe()
//Either[String, Option[Int]] = Right(Some(2))
foo[Effect](0.some).value.runSyncUnsafe()
//Either[String, Option[Int]] = Left("error")
foo[Effect](none).value.runSyncUnsafe()
//Right(None)
Dmytro Orlov
  • 186
  • 4