关于 Scala Futures 的渴望

Regarding Scala Futures being eager

我一直在努力理解为什么 Scala Futures 被认为是急切的并且违反了参照透明性。我想我对这部分的理解是合理的。但是,我很难理解这意味着什么:

(A => Unit) => Unit

关于未来。

我不确定这是否是正确的论坛,但感谢 ELI5 的回答

之所以 Future 被认为是急切的(因此违反了引用透明性),是因为它会在值定义后立即求值。以下是 ELI5 和非 ELI5 对此的解释。

至于(A => Unit) => Unit,它是回调驱动的异步计算的签名。在同步计算中,您将 Future[A] 计算为 A,即使这意味着坐在原地等待很长时间才能完成计算。但是对于异步计算,您不会坐以待毙;相反,您传递类型为 A => Unit 的函数,您会立即得到 Unit 返回。稍后,当计算在后台完成并产生值 A 时,函数 A => Unit 将应用于它。所以基本上你告诉 Future "once you obtain A, here's what I want you to do with it",它会回应 "OK, will do, here's a Unit for you, leave now and do other stuff"。

TBH 我不会想太多这个签名,因为这不是您使用 Future 的心智模型。相反,只需熟悉映射和平面映射的概念即可。当您有一个值包装在 Future 中时,您不应尝试从 Future 上下文中获取该值,因为那将是一个阻塞同步操作。但是你可以做的是映射它并说 "alright Future, I don't need this value A right now, I just want to describe a function A => B to you which turns it to another value B, and you make sure to apply it to once you have the original A"。如果 B 被包裹在另一个 Future 中,这意味着您的函数不是 A => B 而是 A => Future[B],您应该使用 flatMap 而不是映射。这就是链接异步操作的方式。想象一个数据库查询,它作为参数需要在前一个查询中返回一些东西。

就是这样。在世界尽头的某个地方,例如当你处理完一个 http 请求并准备好通过网络发送一些响应有效载荷时,你最终将以同步方式解开那个未来(如果你不知道要放入什么,你就不能发送有效载荷).

现在,关于 Future 中的引用透明度:

ELI5:

假设您有两个女儿,安娜和贝蒂。你告诉他们他们的任务是大声数到 20。你还告诉他们贝蒂应该在安娜完成后才开始。因此,整个过程预计需要大约 40 秒。

但是如果他们急切地评估他们的任务(就像 Future 那样),只要您向他们解释任务,他们就会立即开始计数。因此整个过程将持续约20秒。

在编程的上下文中,引用透明表示您应该始终能够替换(伪代码):

// imagine >> as a pipe operator which starts the next function
// only after previous one has terminated

count(20) >> count(20)

anna = count(20)
betty = count(20)
anna >> betty

但由于急切的评估,在这种情况下情况并非如此(女孩们在向她们解释任务后立即开始计数,因此在第二种情况下,无论管道如何,程序都只会持续 20 秒)。

非 ELI5:

让我们为 Future 准备一个执行上下文和一个将被评估的函数。它只是在打印 "hi".

之前睡了两秒钟
import scala.concurrent.ExecutionContext.Implicits.global

def f = {
  Thread.sleep(2000)
  println("hi")
}

现在让我们写一个 for comprehension,它将一个接一个地创建两个 Future

val done = for {
  f1 <- Future(f)
  f2 <- Future(f)
} yield (f1, f2)

import scala.concurrent.duration._
Await.result(done, 5000 millis)

正如预期的那样,两秒后我们将获得第一个 "hi"(来自 f1),再过两秒后我们将获得第二个 "hi"(来自 f2).

现在我们来做一个小小的修改;我们将首先定义两个 Future 值,然后我们将在理解中使用它们:

val future1 = Future(f)
val future2 = Future(f)

val done = for {
  f1 <- future1
  f2 <- future2
} yield (f1, f2)

import scala.concurrent.duration._
Await.result(done, 5000 millis)

这次发生的情况是,大约两秒钟后,您会得到两个同时 "hi" 打印输出。这是因为 future1future2 都在定义后立即开始求值。当他们在 for comprehension 中被链接时,他们已经 运行 在给定的执行上下文中并排在一起。

这就是引用透明性被破坏的原因;通常你应该能够替换:

doStuff(foo)

val f = foo
doStuff(f)

对程序的行为没有任何影响,但在 Future 的情况下,正如您在上面看到的,情况并非如此。