我试图理解“按需调用”背后的定理。我确实理解这个定义,但我有点困惑。我想看一个简单的例子,它展示了按需调用的工作原理。
在阅读了一些之前的帖子后,我发现 Haskell 使用了这种评估。是否还有其他支持此功能的编程语言?
我阅读了有关 Scala 的按名称调用的信息,并且我确实理解按名称调用和按需要调用是相似的,但不同之处在于按需要调用将保留评估值。但我真的很想看到一个真实的例子(它不必在 Haskell 中),它显示了按需调用。
我试图理解“按需调用”背后的定理。我确实理解这个定义,但我有点困惑。我想看一个简单的例子,它展示了按需调用的工作原理。
在阅读了一些之前的帖子后,我发现 Haskell 使用了这种评估。是否还有其他支持此功能的编程语言?
我阅读了有关 Scala 的按名称调用的信息,并且我确实理解按名称调用和按需要调用是相似的,但不同之处在于按需要调用将保留评估值。但我真的很想看到一个真实的例子(它不必在 Haskell 中),它显示了按需调用。
功能
say_hello numbers = putStrLn "Hello!"
忽略它的numbers
论点。在按值调用语义下,即使忽略参数,也可能需要评估函数调用处的参数,这可能是因为程序的其余部分所依赖的副作用。
在 Haskell 中,我们可以say_hello
称之为
say_hello [1..]
[1..]
自然数的无限列表在哪里。在按值调用语义下,CPU 会试图构建一个无限列表而永远无法到达say_hello
!
Haskell 仅输出
$ runghc cbn.hs
Hello!
对于不太戏剧化的例子,前十个自然数是
ghci> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]
前十个赔率是
ghci> take 10 $ filter odd [1..]
[1,3,5,7,9,11,13,15,17,19]
在按需调用语义下,每个值——即使是上面示例中的概念上无限的值——仅在需要的范围内进行评估,不再进行评估。
更新:一个简单的例子,要求:
ff 0 = 1
ff 1 = 1
ff n = go (ff (n-1))
where
go x = x + x
在按名称调用的情况下,每次调用的go
评估两次,每次在其定义中ff (n-1)
的每次出现(因为在两个参数中都是严格的,即要求它们的值)。x
+
在按需调用下,go
的参数最多被评估一次。具体来说,这里x
' 的值只被找出一次,并x
在表达式的第二次出现时被重用x + x
。如果不需要,则x
根本不会进行评估,就像按名称调用一样。
在按值调用下,go
的参数总是在进入函数体之前只计算一次,即使它没有在函数体的任何地方使用。
在 Haskell 的上下文中,这是我对它的理解。
根据 Wikipedia的说法,“按需要调用是按名称调用的记忆变体,如果对函数参数进行评估,则存储该值以供后续使用。”
按姓名呼叫:
take 10 . filter even $ [1..]
对于一个消费者,生产的价值在生产后就消失了,所以它也可能是点名的。
有需要的来电:
import qualified Data.List.Ordered as O
h = 1 : map (2*) h <> map (3*) h <> map (5*) h
where
(<>) = O.union
不同之处在于,这里的h
列表被多个消费者以不同的速度重用,因此必须记住生成的值。在按名称调用的语言中,这里的计算工作量会有很多复制,因为计算表达式 forh
将在其每次出现时被替换,从而导致对每个计算表达式进行单独计算。在像 Haskell 这样的按需调用语言中,计算 的元素的结果h
在每个对h
.
另一个例子是,大多数由 定义的数据fix
只能在按需调用的情况下使用。使用按值调用,我们最多可以拥有Y组合器。
请参阅:共享与非共享定点组合器及其链接条目和评论(其中,this及其链接,例如Can fold 可用于创建无限列表吗?)。