结合`OptionT`和`EitherT`来处理`Future[Either[Error, Option[T]]]`

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

我想使用 Cats EitherTOptionT 来处理类型 Future[Either[Error, Option[T]]。假设有以下方法:

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

现在,如果我想随后在理解中调用它们,我可以像这样使用 EitherTOptionT

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
}

我想知道是否可以通过将 OptionTEitherT 结合起来使其更简洁?

如果正确理解你的问题,你想构建一个 EitherTOptionT 的组合 monad-transformer。使用猫你可以尝试这样的事情:

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
  }

}

然后您可以将 getSchoolByTeacherId 重写为

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
}

不幸的是,即使 OResult.applyimplicit 你仍然必须在 for-comprehension 的第一行明确地写它,但这允许在后面的行中跳过它。