首页 > 解决方案 > 关于 Scala Futures 的渴望

问题描述

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

(A => Unit) => Unit

关于未来。

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

标签: scalafunctional-programmingfuture

解决方案


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

至于(A => Unit) => Unit,它是回调驱动的异步计算的签名。在同步计算中,您评估Future[A]to A,即使这意味着坐在原地并等待很长时间以完成评估。但是使用异步计算,您不必坐等;相反,你传递一个 type 的函数A => Unit,然后你立即得到Unit返回。稍后,当后台计算完成并产生值时,将对其应用A函数。A => Unit所以基本上你告诉Future“一旦你获得A,这就是我想让你用它做的事情”,它会回答“好的,会做,这是一个给你的单位,现在离开并做其他事情”。

TBH 我不会过多地考虑这个签名,因为这不是你Future应该使用的心理模型。相反,只需熟悉映射和 flatMapping 的概念。当你有一个包含在 a 中的值时Future,你不应该尝试从 Future 上下文中获取该值,因为这将是一个阻塞同步操作。但是你可以做的是映射它并说“好吧Future,我现在不需要这个值A,我只想向你描述一个函数A => B,它将它变成另一个值B,然后你一旦你有原始的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 理解,它将Future一个接一个地创建两个 s:

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值,然后我们将在 for 理解中使用它们:

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,如您在上面看到的,情况并非如此。


推荐阅读