Объединение фьючерсов, Eithers и опций для понимания

У меня есть набор методов, возвращающих разные типы:

Either[ErrorResponse, X]
Future[Either[ErrorResponse, X]]
Option[ErrorResponse]

Эти методы нуждаются в результатах предыдущего метода для выполнения своих вычислений. Методы:

type Parameters = Map[String, String]

// allows me to flatmap on an either
implicit def toRightProjection[Failure, Success](e: Either[Failure, Success]) =
  e.right

// converts anything to a future
implicit def toFuture[T](t: T) =
  Future.successful(t)

// retrieves the request paramters from the given request
def requestParameters(request: RequestHeader): Either[ErrorResponse, Parameters] = ???

// retrieves the response type from the given parameters
def responseType(p: Parameters): Either[ErrorResponse, String] = ???

// retrieves the client id from the given parameters
def clientId(p: Parameters): Either[ErrorResponse, String] = ???

// retrieves the client using the given client id
def client(clientId: String): Future[Either[ErrorResponse, Client]] = ???

// validates the response type of the client
def validateResponseType(client: Client, responseType: String): Option[ErrorResponse] = ???

Я могу связать их вместе со следующим для понимания (обратите внимание, что я записал несколько типов, чтобы уточнить содержимое конкретных частей вычисления).

val result: Either[ErrorResponse, Future[Either[ErrorResponse, Client]]] =
  for {
    parameters <- requestParameters(request)
    clientId <- clientId(parameters)
    responseType <- responseType(parameters)
  } yield {
    val result: Future[Either[ErrorResponse, Either[ErrorResponse, Client]]] =
      for {
        errorOrClient <- client(clientId)
        client <- errorOrClient
      } yield validateResponseType(client, responseType).toLeft(client)

    result.map(_.joinRight)
  }

val wantedResult: Future[Either[ErrorResponse, Client]] =
  result.left.map(Future successful Left(_)).merge

Вышеприведенный код довольно грязный, и я чувствую, что это можно сделать по-другому. Я читал о монадах и монадных трансформаторах. Понятие этих вещей для меня очень нова, и я не могу обдумать его.

Большинство примеров относятся только к двум типам результатов: Either[X, Y] и Future[Either[X, Y]]. Мне все еще очень сложно сгибать мысли.

Как я могу написать красивое и чистое для понимания, которое заменяет предыдущее?

Что-то вроде этого было бы потрясающе (я не уверен, что это возможно):

val result: Future[Either[ErrorResponse, Client]] =
  for {
    parameters <- requestParameters(request)
    clientId <- clientId(parameters)
    responseType <- responseType(parameters)
    client <- client(clientId)
    _ <- validateResponseType(client, responseType)
  }

Ответ 1

ОК, вот моя попытка:

import scalaz._, Scalaz._

implicit val futureMonad = new Monad[Future] {
  override def point[A](a: ⇒ A): Future[A] = future(a)

  override def bind[A, B](fa: Future[A])(f: A ⇒ Future[B]): Future[B] =
    fa.flatMap(f)
}

import EitherT._
val result: EitherT[Future, ErrorResponse, Client] =
  for {
    parameters <- fromEither(Future(requestParameters(request)))
    clientId <- fromEither(Future(clientId(parameters)))
    responseType <- fromEither(Future(responseType(parameters)))
    client <- fromEither(client(clientId))
    response <- fromEither[Future, ErrorResponse, Client](Future(validateResponseType(client, responseType).toLeft(client)))
  } yield response

val x: Future[\/[ErrorResponse, Client]] = result.run

Ответ 2

scala.util.Either - это не Монада, но библиотека скаляз имеет большую реализацию.

object Test extends ToIdOps {

import scalaz.{ Monad, Functor, EitherT, \/, -\/, \/- }
import scalaz.syntax.ToIdOps

implicit val FutureFunctor = new Functor[Future] {
    def map[A, B](a: Future[A])(f: A => B): Future[B] = a map f
}

implicit val FutureMonad = new Monad[Future] {
  def point[A](a: => A): Future[A] = Future(a)
  def bind[A, B](fa: Future[A])(f: (A) => Future[B]): Future[B] = fa flatMap f
}
def someMethod: Future[\/[InvalidData, ValidData]] = {
   // things went well
   ValidData.right // this comes from ToIdOps
   // or something went wrong
   InvalidData.left
}
def someOtherMethod: Future[\/[InvalidData, ValidData]] // same as above
val seq = for {
  d <- EitherT(someMethod)
  y <- EitherT(someOtherMethod)
} yield { // whatever}
// you can now Await.result(seq.run, duration)
// you can map or match etc with \/- and -\/
val result = seq.run map {
   case -\/(left) => // invalid data
   case \/-(right) => // game on
}
}

Ответ 3

Не существует действительно чистого способа сделать понимание нескольких типов монадов. В ScalaZ есть OptionT, который может помочь, стоит проверить. Вы также можете преобразовать свои Eithers в опции или наоборот, и иметь возможность немного меньше беспорядка. Третий вариант может состоять в том, чтобы создать свою собственную оболочку, которая объединяет Future [Either | Option] в ту же монаду, а затем постигает ее.

Для справки я задал вопрос о том же вопросе в списке рассылки для платформы воспроизведения в последнее время и получил некоторые хорошие ссылки в ответах: https://groups.google.com/d/topic/play-framework/JmCsXNDvAns/discussion