介绍
Scala的Future
(从2.10版本开始,现在已经到了2.9.3版本) 是一个可应用函子(applicative functor),这意味着如果我们有一个可遍历类型F
,我们可以取一个F[A]
和一个函数A => Future[B]
并将它们转换成一个Future[F[B]]
。
Future.traverse
。Scalaz 7 还提供了一个更通用的 traverse
方法,如果我们从 scalaz-contrib
库 导入了 Future
的 applicative functor 实例,我们就可以在这里使用它。这两种
traverse
方法在处理流时表现不同。标准库的遍历会在返回之前消耗掉流,而 Scalaz 的 会立即返回 future:import scala.concurrent._
import ExecutionContext.Implicits.global
// Hangs.
val standardRes = Future.traverse(Stream.from(1))(future(_))
// Returns immediately.
val scalazRes = Stream.from(1).traverse(future(_))
另外还有一个区别,正如Leif Warner在这里所观察到的那样。标准库的
traverse
会立即启动所有异步操作,而Scalaz的则会启动第一个,等待它完成,然后开始第二个,再等待它完成,以此类推。
对于流的不同行为
很容易通过编写一个函数来展示这第二个区别,该函数将在流中的第一个值上睡眠几秒钟:def howLong(i: Int) = if (i == 1) 10000 else 0
import scalaz._, Scalaz._
import scalaz.contrib.std._
def toFuture(i: Int)(implicit ec: ExecutionContext) = future {
printf("Starting %d!\n", i)
Thread.sleep(howLong(i))
printf("Done %d!\n", i)
i
}
现在,
Future.traverse(Stream(1, 2))(toFuture)
将打印以下内容:Starting 1!
Starting 2!
Done 2!
Done 1!
而 Scalaz 版本 (Stream(1, 2).traverse(toFuture)
):
Starting 1!
Done 1!
Starting 2!
Done 2!
这可能不是我们想要的。
列表呢?
奇怪的是,在列表上,这两个遍历方式在这方面的行为是相同的 - Scalaz 的遍历方式不会等待一个未来完成后再开始下一个。
另一个未来
Scalaz 还包括自己的 concurrent
包,其中包含了自己的未来实现。我们可以使用与上面相同的设置:
import scalaz.concurrent.{ Future => FutureZ, _ }
def toFutureZ(i: Int) = FutureZ {
printf("Starting %d!\n", i)
Thread.sleep(howLong(i))
printf("Done %d!\n", i)
i
}
然后,我们得到了Scalaz对于列表和流的行为:
Starting 1!
Done 1!
Starting 2!
Done 2!
也许不出所料的是,遍历无限流仍然会立即返回。
问题:
此时我们真的需要一个表来总结,但只能用列表:
- 使用标准库遍历的流:在返回之前消耗;不等待每个 future。 - 使用 Scalaz 遍历的流:立即返回;等待每个 future 完成。 - 使用流的 Scalaz futures:立即返回;等待每个 future 完成。
以及:
- 使用标准库遍历的列表:不等待。 - 使用 Scalaz 遍历的列表:不等待。 - 使用列表的 Scalaz futures:等待每个 future 完成。
这有意义吗?在列表和流上执行此操作是否存在“正确”的行为?是否有某种原因使得“最异步”的行为——即在返回之前不消耗集合,并且在移动到下一个之前不等待每个 future 完成——在这里没有被表示?
traverse
返回一个Future [Stream [B]]
——这一部分是不容置疑的。问题是语义应该是什么。 - Travis Brownfor
:) - soulcheck