Monads 是一种排序计算的机制,下面的列表仍然是 monad,尽管它们是以随机顺序打印的 Scala

Monads being a mechanism for sequencing computations, is the below list still a monad though they are printed in a random order Scala

for {
    i <- 1 to 5
  } yield Future(println(i))

脱糖至:

List(1,2,3,4,5).map {i => Future(println(i))}

以上代码以随机顺序打印数字。

现在,如果我们看到 Monad 的多个定义: a) Monad 是对象的包装器 b) Monad 是一种排序计算的机制

我想回答的问题是,List monad 上的 map 操作不应该等待列表中的第一个元素被打印出来,然后才去计算第二个元素而不考虑 Future?

抱歉,这可能很简单,我把它复杂化了,但找到简单的推理对我来说变得更加棘手。答案将不胜感激:)

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(与 Ca​​ts Effect 中的 IO、Monix 的 Task 或 ZIO 相对)是急切的,因此它会在您创建它的那一刻开始执行。出于这个原因,前两个示例中有顺序结果,第三个示例中有随机顺序(竞争条件)。

如果您使用 IO 而不是 Future 它会更明显,因为您不能只使用 List[IO[Unit]] 并执行副作用 - 您将不得不以某种方式将不同的 IOs 合二为一,您将采用的方式可以清楚地表明效果是连续的还是平行的。

底线是 - Future 是否是 monad 取决于 .flatMap 的行为方式(以及它与 Future.successful 结合时的行为方式),因此您的结果不't 使 Future 是 monad 的声明无效。 (如果你开始检查它的异常行为,你可能会有一些疑问,但那是另一个话题)。

从广义上讲,您的两个代码片段仍然是 Monad。当您对对象执行 .map() 时,地图会按顺序(从索引 0 到索引 4)一个接一个地选取元素。然后它将其传递给一个操作块(它是 map 的主体 - map 是一个高阶函数,它接受类型为 f:This => That 的函数)。

所以 monad 操作的职责是获取它并将其作为参数传递给函数。

在你的例子中,实际函数类型是:

f: Int => Future[Unit]

为清楚起见,您的函数实际上如下所示:

def someFunction(i: Int): Future[Unit] = {
    Future {
        println(i)
    }
}

因此,地图操作在这里所做的是它从您的对象中挑选项目(按顺序,一个接一个)并调用 someFunction(i)。这就是 monad 所做的一切。

现在回答为什么你的 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 可能会或可能不会乱序。这种无序的时尚不是因为 monad 操作 map,而是因为多核 CPU 中的多线程特性。