介绍
Scala Future
(2.10和现在 2.9.3中的新功能)是一个应用函子,这意味着如果我们有一个可遍历的类型 F
,我们可以将一个F[A]
和一个函数A => Future[B]
转换为一个Future[F[B]]
.
此操作在标准库中作为Future.traverse
. Scalaz 7还提供了一个更通用的方法,如果我们从库traverse
中导入 applicative functor 实例,我们可以在这里使用它。Future
scalaz-contrib
这两种traverse
方法在流的情况下表现不同。标准库遍历在返回之前消耗流,而 Scalaz立即返回未来:
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!
也许不那么令人惊讶的是,遍历无限流仍然会立即返回。
问题
此时我们确实需要一个表格来总结,但必须要有一个列表:
- 带标准库遍历的流:在返回前消费;不要等待每个未来。
- 带有Scalaz遍历的流:立即返回;等待每个未来完成。
- Scalaz 带有流的期货:立即返回;等待每个未来完成。
和:
- 具有标准库遍历的列表:不要等待。
- 带有 Scalaz 遍历的列表:不要等待。
- 带有列表的 Scalaz 期货:请等待每个未来完成。
这有道理吗?列表和流上的此操作是否存在“正确”行为?是否有某种原因,“最异步”的行为——即在返回之前不要使用集合,并且不要等待每个未来完成后再继续下一个——这里没有表示?