for {
i <- 1 to 5
} yield Future(println(i))
Desugared to:
List(1,2,3,4,5).map {i => Future(println(i))}
上述代码打印的数字是 随机顺序.
现在,如果我们看到Monad的多种定义。a)Monad是一个对象的包装器。b) 单元组是一种排序计算的机制。
我想回答的问题是,对List单体的map操作是不是应该等待列表中的第一个元素被打印出来,然后才去计算第二个元素,而不考虑Future?
对不起,这可能很简单,我把它复杂化了,但对我来说,要找到简单的推理会变得更棘手。希望能得到答案:)
比较。
for {
_ <- Future(println(1))
_ <- Future(println(2))
_ <- Future(println(3))
_ <- Future(println(4))
_ <- Future(println(5))
} yield ()
或
Future(println(1)).flatMap { _ =>
Future(println(2))
}.flatMap { _ =>
Future(println(3))
}.flatMap { _ =>
Future(println(4))
}.flatMap { _ =>
Future(println(5))
}
与
List(
Future(println(1)),
Future(println(2)),
Future(println(3)),
Future(println(4)),
Future(println(5))
)
前两个创造下一个 Future
只有在前者完成并提供结果后,才可以使用。最后一个是创建所有的 Future
(在这一点上,它与您的例子中的 List[Future]
).
Future
(与 IO
来自猫咪效应,Monix的 Task
或ZIO)是急切的,所以它在你创建它的时候就开始执行。出于这个原因,你在前两个例子中的结果是顺序的,而在第三个例子中的结果是随机顺序(竞赛条件)。
如果你使用 IO
而不是 Future
这将是更明显的,因为你不可能只拥有 List[IO[Unit]]
并执行副作用--你必须以某种方式将不同的IO组合成一个,而你这样做的方式将使效果是顺序的还是并行的变得很明显。
底线是--是否 Future
是一个单体,取决于 .flatMap
的行为(以及它在与 Future.successful
),所以你的结果并不能使你的说法失效。Future
是一个单项式.如果你开始用异常检查它的行为,你可以有一些疑问。(如果你开始用异常检查它的行为,你会有一些疑问,但这是另一个话题)。
map的执行确实是顺序的,但是当你把它包到Future时,它就会以异步的方式执行,我的意思是它会在另一个线程中被评估,正因为如此,不可能知道哪个线程会更早完成,因为它还取决于操作系统的线程管理和其他考虑因素。
你的这两个代码片段从宽泛的角度来说还是Monads。当你做 .map()
在你的对象上,map按顺序一个一个地选取元素(从索引0到索引4),然后它将这些元素传递给操作块(这是map的主体--map是一个高阶函数,接受类型为f:This => That)。然后将其传递给一个操作块(它是map的主体--map是一个高阶函数,接受f:This => That类型的函数)。
所以单项式操作的责任是将其拾取并作为paramater传递给一个函数。
在你的例子中,实际的函数类型是。
f: Int => Future[Unit]
为了清楚起见,你的函数实际上是这样的。
def someFunction(i: Int): Future[Unit] = {
Future {
println(i)
}
}
所以,映射操作在这里做的是它从你的对象中挑选出一个项目(按顺序,一个接一个) 然后调用someFunction(i). 这就是一个单体所做的一切。
现在来回答为什么你的 println 是随机的,这是因为 JVM线程.
如果你重新定义你的地图主体,就像这样。
List(1,2,3,4,5)
.map {i =>
println(s"Going to invoke the println in another thread for $i")
Future(println(i))
}
你会发现,第一个 println
将按顺序进行--始终如此! 它证明了 .map()
按顺序选择你的元素。而下一个 println
可能不按顺序,也可能不按顺序。这种失序的时尚并不是因为单体操作。map
但由于多核CPU的多线程特性。