如何在给定的 Future[Try[Option[A]]] 上应用类型 A => Future[Try[Option[B]]] 的函数?

How to apply function of type A => Future[Try[Option[B]]] on given Future[Try[Option[A]]]?

我想简化函数应用,以便我可以在元素上应用 f 类型的函数:A => Future[Try[Option[B]]]:Future[Try[Option[A]]]

简而言之,我需要以下函数的定义:

def transformation(f: A => Future[Try[Option[B]]])
                       (element: Future[Try[Option[A]]]): Future[Try[Option[B]]] = ???

谢谢。

因为涉及不同的 monad,而且(据我所知)Future[Try[Option]]] 没有 monad 转换器,您需要为每种情况手动映射和匹配:

def transformation[A, B](f: A => Future[Try[Option[B]]])(element: Future[Try[Option[A]]]): Future[Try[Option[B]]] = element.flatMap {
    case Success(Some(b)) => f(b)
    case Success(None) => Future.successful(Success(None))
    case Failure(fail) => Future.successful(Failure(fail))
}

如果你必须经常做这样的事情——你可以考虑使用外部库,比如 Emm+cats(或 scalaz),基本上它们提供了自动 monad 转换器:

import $ivy.`org.typelevel::cats:0.9.0`
import $ivy.`com.codecommit::emm-core:0.2.1`
import $ivy.`com.codecommit::emm-cats:0.2.1`

import emm._
import scala.concurrent._
import scala.util._
import cats._
import emm.compat.cats._
import cats.implicits._
type E = Future |: Try |: Option |: Base
import scala.concurrent.ExecutionContext.Implicits.global


def transformation[A, B](f: A => Future[Try[Option[B]]])
                       (element: Future[Try[Option[A]]]): Future[Try[Option[B]]] = {
  val effect: Emm[E, B] = for {
    elTry <- element.liftM[E]
    elOption <- elTry.liftM[E]
    el <- elOption.liftM[E]
    resTry <- f(el).liftM[E]
    resOption <- resTry.liftM[E]
    res <- resOption.liftM[E]
  } yield res    
  effect.run
}

基本上,当 monad 具有一致的类型时,这就是在正常情况下使用 for 所做的,只需简单地添加 liftM。简短版本:

def transformation[A, B](f: A => Future[Try[Option[B]]])
                       (element: Future[Try[Option[A]]]): Future[Try[Option[B]]] = 
  element
    .liftM[E]
    .flatMap(_.liftM[E]).flatMap(_.liftM[E]) //unpack A
    .map(f) //apply transformation
    .flatMap(_.liftM[E]).flatMap(_.liftM[E]).flatMap(_.liftM[E]) //unpack B
    .run

您可能想要使用 monad 转换器。你的类型

Future[Try[Option[A]]]

等同于

OptionT[EitherT[Future, Throwable, ?], A]

? 语法来自 kind-projector 编译器插件。)

让我们一步一步地观察这个。

首先,Try[A] 等价于析取 Throwable \/ A(其中 \/ 只是 scalaz 等价于 Either)。我们可以通过 toDisjunction, fromDisjunction 在它们之间进行翻译。这给了我们

Future[Try[Option[A]]]

等同于

Future[Throwable \/ Option[A]]

接下来,知道F[A \/ B]EitherT[F, A, B]definition,我们得到上面等价于

EitherT[Future, Throwable, Option[A]]

最后,注意到 OptionT[F, A]definitionF[Option[A]],将 F[?] 设为 EitherT[Future, Throwable, ?] 我们得出结论,上面等价于

OptionT[EitherT[Future, Throwable, ?], A]

我们从这个表示中得到了什么?

现在你的 transformation 函数就是 flatMap.

type Effect[A] = OptionT[EitherT[Future, Throwable, ?], A]

def transformation1[A, B](f: A => Effect[B])(element: Effect[A])
                         (implicit ec: ExecutionContext): Effect[B] =
  element.flatMap(f)

要使用您的原始签名(即与 Future[Try[Option[A]]] 一起使用的签名)获得 transformation 函数,我们需要定义与 EffectEffect 之间的转换方法。

def toEffect[A](a: Future[Try[Option[A]]])(implicit ec: ExecutionContext): Effect[A] = ???
def fromEffect[A](a: Effect[A])(implicit ec: ExecutionContext): Future[Try[Option[A]]] = ???

def transformation[A, B](f: A => Future[Try[Option[B]]])
                        (element: Future[Try[Option[A]]])
                        (implicit ec: ExecutionContext): Future[Try[Option[B]]] =
  fromEffect(for {
    a <- toEffect(element)
    b <- toEffect(f(a))
  } yield b)

但也许您可能希望在整个应用程序中使用 monad 转换器表示,而不是来回转换。

完整代码

这是完整的代码,包括导入。我用 scalaz 7.3.0-M10 测试了它。

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import scalaz._
import scalaz.std.scalaFuture._
import scalaz.std.`try`._
import scalaz.syntax.std.`try`._

type Effect[A] = OptionT[EitherT[Future, Throwable, ?], A]

def toEffect[A](a: Future[Try[Option[A]]])(implicit ec: ExecutionContext): Effect[A] = {
  val b: Future[Throwable \/ Option[A]] = a.map(_.toDisjunction)
  val c: EitherT[Future, Throwable, Option[A]] = EitherT(b)
  val d: OptionT[EitherT[Future, Throwable, ?], A] = OptionT[EitherT[Future, Throwable, ?], A](c)
  d
}

def fromEffect[A](a: Effect[A])(implicit ec: ExecutionContext): Future[Try[Option[A]]] =
  a.run.run.map(fromDisjunction(_))

def transformation1[A, B](f: A => Effect[B])(element: Effect[A])
                         (implicit ec: ExecutionContext): Effect[B] =
  element.flatMap(f)

def transformation[A, B](f: A => Future[Try[Option[B]]])
                        (element: Future[Try[Option[A]]])
                        (implicit ec: ExecutionContext): Future[Try[Option[B]]] =
  fromEffect(for {
    a <- toEffect(element)
    b <- toEffect(f(a))
  } yield b)