Combining `OptionT` and `EitherT` to handle `Future[Either[Error, Option[T]]]`
Asked Answered
S

1

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?

Springbok answered 28/12, 2017 at 16:18 Comment(2)
What benefit would you expect from using OptionT too (which you do not use in your example)?Aloise
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)Springbok
L
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.

Lemos answered 29/12, 2017 at 3:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.