按需调用:何时在Haskell中使用?

时间:2012-04-28 08:48:21

标签: haskell lazy-evaluation

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need说:
“按需调用是一个mem-by-name的memoized版本,其中,如果评估函数参数,则存储该值以供后续使用。[...] Haskell是使用call的最着名的语言 - 按需评估。“

但是,并不总是存储计算的值以便更快地访问(例如,考虑斐波纳契数的递归定义)。我在#haskell问了一个人,回答是这个记忆是自动完成的“只在一个例子中,例如,如果你有'让foo = bar baz',foo将被评估一次”。

我的问题是:实例究竟意味着什么,是否存在其他情况,而不是自动完成记忆?

3 个答案:

答案 0 :(得分:7)

将此行为描述为“memoization”具有误导性。 “通过需要调用”只意味着函数的给定输入将在0到1次之间进行评估,绝不会超过一次。 (也可以部分评估,这意味着该功能仅需要该输入的一部分。)相反,“通过名称呼叫”只是表达式替换,这意味着如果将表达式2 + 3作为函数的输入,如果输入被多次使用,则可以多次计算它。按需调用和按名称调用都是非严格:如果未使用输入,则永远不会对其进行评估。大多数编程语言是严格,并使用“通过调用”方法,这意味着在开始评估函数之前评估所有输入,无论是否使用输入。这一切都与let表达无关。

Haskell不执行任何自动记忆。让表达式不是memoization的一个例子。但是,大多数编译器将以按需调用的方式评估let绑定。如果将let表达式建模为函数,则“按需调用”心态 适用:

let foo = expression one in expression two that uses foo
==>
(\foo -> expression two that uses foo) (expression one)

这不能正确地模拟递归绑定,但是你明白了。

答案 1 :(得分:6)

haskell语言定义不定义调用代码的时间或频率。无限循环是根据“底部”(书面⊥)定义的,它是一个表示错误条件的值(存在于所有类型中)。只要程序(以及错误条件的存在/不存在,包括无限循环!)根据规范行事,编译器就可以自由决定何时以及多久评估一次。

也就是说,通常的做法是大多数表达式生成“thunks” - 基本上是指向某些代码和一些上下文数据的指针。第一次尝试检查表达式的结果(即模式匹配)时,thunk是“强制”的;执行指向的代码,并用真实数据覆盖thunk。这反过来可以递归评估其他thunk。

当然,这样做总是很慢,所以编译器通常会尝试分析你最后是什么时候立即强迫thunk(即,当某个东西对所讨论的值有'严格'时),并且如果它找到了这个,它将跳过整个thunk事件并立即调用代码。如果它不能证明这一点,它仍然可以进行这种优化,只要它确保立即执行thunk不会崩溃或导致无限循环(或它以某种方式处理这些条件)。

答案 2 :(得分:0)

如果你不想对此有所了解,那么关键点在于当你有一个像some_expensive_computation of all these arguments这样的表达时,你可以随心所欲地做任何事情;将它存储在数据结构中,创建一个包含53个副本的列表,将其传递给其他6个函数等,然后甚至将其返回给调用者,以便调用者用它做任何想做的事情。

Haskell(大多数)会做的最多是评估一次;如果程序需要知道某个表达式返回的内容以便做出决定,那么它将被评估(至少足以知道决策应该采用哪种方式)。该评估将影响对同一表达式的所有其他引用,即使它们现在分散在整个程序中的数据结构和其他尚未评估的表达式中。