8

I want to use Cats EitherT and OptionT to handle the type Future[Either[Error, Option[T]]. Suppose the following methods:

def findTeacher(id: Int): Future[Either[String, Option[Teacher]]]
def findSchool(teacher: Teacher): Future[Either[String, Option[School]]]

Now if I want to call them subsequently in a for-comprehension I can use EitherT and OptionT like this:

def getSchoolByTeacherId(id: Int): Future[Either[String, Option[School]]] = {
  val result = for {
    maybeTeacher <- EitherT(findTeacher(id))
    schoolF = maybeTeacher.map(findSchool).getOrElse(Future.successful(Right(None)))
    school <- EitherT(schoolF)
  } yield {
    school
  }

  result.value
}

I wonder if it's possible to make it more concise maybe by combining OptionT with EitherT?

Amir Karimi
  • 5,401
  • 4
  • 32
  • 51
  • What benefit would you expect from using `OptionT` too (which you do not use in your example)? – Gábor Bakos Dec 28 '17 at 17:28
  • Just looking for more concise way instead of the second line of for-comprehension. Or maybe it can be rewritten in a different way using some methods or types of Cats that I'm not aware of (not necessarily using for-comprehension) – Amir Karimi Dec 28 '17 at 23:34

1 Answers1

8

If understand your question correctly, you want to build a combined monad-transformer of EitherT and OptionT. Using Cats you may try something like this:

type FutureEither[X] = EitherT[Future, String, X]
type OResult[X] = OptionT[FutureEither, X]

object OResult {

  implicit def apply[A](value: Future[Either[String, Option[A]]]): OResult[A] = OptionT[FutureEither, A](EitherT(value))

  implicit class OResultOps[A](val value: OResult[A]) extends AnyVal {
    @inline
    def directValue: Future[Either[String, Option[A]]] = value.value.value
  }

}

And then you may re-write your getSchoolByTeacherId as

import OResult._
def getSchoolByTeacherId(id: Int): Future[Either[String, Option[School]]] = {
  val result = for {
    teacher <- OResult(findTeacher(id))
    school <- findSchool(teacher)
  } yield school

  result.directValue
}

Unfortunately, even though OResult.apply is implicit you still have to write it explicitly in the first line of your for-comprehension but this allows skipping it on the further lines.

SergGr
  • 23,570
  • 2
  • 30
  • 51