按需调用的简单示例
Simple example of call-by-need
我正在尝试理解背后的定理 "call-by-need." 我确实理解定义,但我有点困惑。我想看一个简单的例子,它展示了按需调用是如何工作的。
在阅读了之前的一些帖子后,我发现 Haskell 使用了这种评估。是否有任何其他编程语言支持此功能?
我了解了 Scala 的按名称调用,并且我确实理解按名称调用和按需要调用是相似的,但不同之处在于按需要调用将保持评估价值。但我真的很想看到一个真实的例子(它不必在 Haskell 中),它显示了按需调用。
函数
say_hello numbers = putStrLn "Hello!"
忽略其 numbers
参数。在 call-by-value 语义下,即使参数被忽略,函数调用站点的参数也可能需要计算,这可能是因为程序其余部分所依赖的副作用。
在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]
在 call-by-need 语义下,每个值——即使是上面示例中概念上无限的值——仅在需要的范围内进行评估,不会超过。
更新:一个简单的例子,要求:
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 的背景下对它的理解。
According to Wikipedia, "call by need is a memoized variant of call by name where, if the function argument is evaluated, that value is stored for subsequent uses."
按姓名呼叫:
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
列表被多个消费者以不同的节奏重复使用,因此必须记住生成的值。在按名称调用的语言中,这里会有很多计算工作的复制,因为 h
的计算表达式将在其每次出现时被替换,从而导致对每个单独的计算。在按需调用--capable 语言中,如 Haskell 计算 h
元素的结果在对 h
的每个引用之间共享.
另一个例子是,大多数由fix
定义的数据只能在按需调用的情况下使用。对于按值调用,我们最多只能使用 Y 组合器。
参见: and its linked entries and comments (among them, this, and its links, like Can fold be used to create infinite lists?)。
我正在尝试理解背后的定理 "call-by-need." 我确实理解定义,但我有点困惑。我想看一个简单的例子,它展示了按需调用是如何工作的。
在阅读了之前的一些帖子后,我发现 Haskell 使用了这种评估。是否有任何其他编程语言支持此功能?
我了解了 Scala 的按名称调用,并且我确实理解按名称调用和按需要调用是相似的,但不同之处在于按需要调用将保持评估价值。但我真的很想看到一个真实的例子(它不必在 Haskell 中),它显示了按需调用。
函数
say_hello numbers = putStrLn "Hello!"
忽略其 numbers
参数。在 call-by-value 语义下,即使参数被忽略,函数调用站点的参数也可能需要计算,这可能是因为程序其余部分所依赖的副作用。
在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]
在 call-by-need 语义下,每个值——即使是上面示例中概念上无限的值——仅在需要的范围内进行评估,不会超过。
更新:一个简单的例子,要求:
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 的背景下对它的理解。
According to Wikipedia, "call by need is a memoized variant of call by name where, if the function argument is evaluated, that value is stored for subsequent uses."
按姓名呼叫:
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
列表被多个消费者以不同的节奏重复使用,因此必须记住生成的值。在按名称调用的语言中,这里会有很多计算工作的复制,因为 h
的计算表达式将在其每次出现时被替换,从而导致对每个单独的计算。在按需调用--capable 语言中,如 Haskell 计算 h
元素的结果在对 h
的每个引用之间共享.
另一个例子是,大多数由fix
定义的数据只能在按需调用的情况下使用。对于按值调用,我们最多只能使用 Y 组合器。
参见: