问题 有没有办法压缩不同类型的嵌套monad?


我不知道如何描述这个问题,所以我只会显示类型签名。

我有以下实例:

val x:Future[F[Future[F[B]]]] = ???

我想要一个例子:

val y:Future[F[B]] = ???

F 是Monad,所以我有以下方法:

def pure[A](a:A):F[A] = ???
def flatMap[A, B](fa:F[A], f:A => F[B]):F[B] = ???
def map[A, B](fa:F[A], f:A => B):F[B] = flatMap(fa, (a:A) => pure(f(a)))

我认为以下内容应该有效,但感觉不对:

x.flatMap { fWithFuture =>
  val p = Promise[F[B]]
  flatMap(fWithFuture, (futureF: Future[F[B]]) => {
    p.completeWith(futureF)
    pure(())
  })
  p.future
}

我缺少一个概念吗?

一些背景信息。我试图定义这样的函数:

def flatMap[A, B](fa:Future[F[A]], f: A => Future[F[B]]):Future[F[B]] = ???

也许这在概念上是一件奇怪的事情。欢迎任何关于有用抽象的提示。


6153
2017-07-21 21:37


起源

FWIW,我在使用你在这里的蛮力方法之前已经在代码中解决了这个问题。我同样感觉不对,但我还没有想出更好的东西。 - joescii
欢迎来到monad变形金刚的笨拙和笨拙的世界! - Rex Kerr
@RexKerr你说这是这样做的吗? - EECOLOR
我更喜欢在monad的肮脏面上玩耍:他们没有优雅地嵌套。您的解决方案是一条出路。 Monad变换器是另一种,但它们有各种缺点(主要是额外的概念开销加上创建它们所需的繁忙工作)。 - Rex Kerr


答案:


正如Rex Kerr在上面指出的那样,你经常可以使用monad变换器来处理你发现自己有像这样的交替层的情况。例如,如果 F 这是 Option,你可以使用 Scalaz 7.1OptionT monad变压器写你的 flatMap

import scalaz._, Scalaz._

type F[A] = Option[A]

def flatMap[A, B](fa: Future[F[A]], f: A => Future[F[B]]): Future[F[B]] =
  OptionT(fa).flatMap(f andThen OptionT.apply).run

OptionT[Future, A] 这是一种包装 Future[Option[A]]。如果你的 F 是 List,只需更换 OptionT 同 ListT 和 run 同 underlying (等等)。

好的是,当你和你一起工作时 OptionT[Future, A]例如,你通常可以避免最终结果 Future[Option[Future[Option[A]]]] 首先 - 看我的答案 这里 进行更详细的讨论。

一个缺点是并非所有的monad都有变压器。例如,你可以把 Future 在堆栈的底部(正如我上面所做的那样),但是没有真正有用的定义方法 FutureT


7
2017-07-22 03:05



“但是没有真正有用的方法来定义 FutureT“为什么?这实际上是我需要的。 - EECOLOR
它的 没有阻止就无法定义。在你工作的时候 Future[Option[A]] 相应的monad变压器是 OptionT[Future, A]不是 FutureT[Option, A] - 你确定你需要 FutureT? - Travis Brown
我不确定我是否需要 FutureT。我唯一知道的是我知道我有一个 Future 还有一些内在的任意monad ......我将回到代码中,看看我是否能够以一种允许我跳过 FutureT。 - EECOLOR


这可能会回答“我想要一个实例:”部分。

$ scala
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_05).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> Future(List(Future(1),Future(2),Future(3))) // Future[F[Future[B]]]
res0: scala.concurrent.Future[List[scala.concurrent.Future[Int]]] = scala.concurrent.impl.Promise$DefaultPromise@41ab013

scala> res0.map(Future.sequence(_)) // transformed to Future[Future[F[B]]
res1: scala.concurrent.Future[scala.concurrent.Future[List[Int]]] = scala.concurrent.impl.Promise$DefaultPromise@26a4842b

scala> res1.flatMap(identity) // reduced to Future[F[B]]
res2: scala.concurrent.Future[List[Int]] = scala.concurrent.impl.Promise$DefaultPromise@4152d38d

希望下面的flatMap定义应该给出转换类型:)的想法 为了便于理解,我将F替换为List类型。

scala>   def flatMap[A, B](fa:Future[List[A]], f: A => Future[List[B]]):Future[List[B]] = {
     |     val x: Future[List[Future[List[B]]]] = fa.map(_.map(f))
     |     val y: Future[Future[List[List[B]]]] = x.map(Future.sequence(_))
     |     val z: Future[Future[List[B]]] = y.map(_.map(_.flatten))
     |     z.flatMap(identity)
     |   }
flatMap: [A, B](fa: scala.concurrent.Future[List[A]], f: A => scala.concurrent.Future[List[B]])scala.concurrent.Future[List[B]]

2
2017-07-22 00:05



sequence 需要一个 TraversableOnce (在你的情况下是一个 List),所以它不适用于其他类型 - Gabriele Petronella
在本质上 sequence 做同样的事情。它只使用 CanBuildFrom 隐藏变异。 - EECOLOR