【问题标题】:How to apply function of type A => Future[Try[Option[B]]] on given Future[Try[Option[A]]]?如何在给定的 Future[Try[Option[A]]] 上应用 A => Future[Try[Option[B]]] 类型的函数?
【发布时间】:2017-04-04 10:30:58
【问题描述】:

我想简化函数应用,以便我可以在元素上应用 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]]] = ???

谢谢。

【问题讨论】:

    标签: scala functional-programming scalaz


    【解决方案1】:

    因为涉及到不同的 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))
    }
    

    【讨论】:

    • 谢谢,这看起来不错...但是它无法推断最内层元素的类型...所以对于该行:case Success(Some(b)) => f(b) ... ...它说 b 的类型是 Any.... 但它不是。
    • 我不知道你在做什么,但这对我来说是编译的,请注意我必须将类型 A 和 B 添加到方法定义中,因为从你的问题中看不到它们。
    • @EndeNeu 请参阅我对等效于 Future[Try[Option]]] 的单子转换器的回答。
    【解决方案2】:

    您可能想要使用 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)
    

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

    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)
    

    【讨论】:

    • 感谢您的回复,这看起来也很有趣。但是,我是 scalaz 库的新手。您能否告诉我为很少或没有 FP 背景的初学者学习 scalaz 的最佳资源?
    • 这是 2-3 年前的我。 Functional Programming in Scala a.k.a.红皮书是对 FP 的一个很好的介绍(并且恰好使用 Scala 作为语言)。 Learning Scalaz 是 Scalaz 的一个很好的介绍。之后,您应该对学习 scalaz 代码感到有些自在。起初 scalaz 几乎没有文档令人沮丧,但后来我意识到很多时候类型签名包含我需要的所有信息。
    • 谢谢一定会看看的。
    【解决方案3】:

    如果你必须经常做这样的事情——你可以考虑使用外部库,比如 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
    

    【讨论】:

    • 什么工具支持'$ivy'语法导入?
    • @user2359636 ammonite shell,它既可以作为 sbt 的插件,也可以作为独立的 shell。并且 jupyter notebook 的 scala 内核也修改了 ammonite - ammonium 的版本。
    猜你喜欢
    • 1970-01-01
    • 2021-04-20
    • 2020-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-27
    • 2015-11-03
    相关资源
    最近更新 更多