efi*_*fie 4 haskell lazy-evaluation
http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need说:
"按需调用是一个按名称调用的memoized版本,其中,如果评估函数参数,则存储该值以供后续使用. [...] Haskell是使用按需调用评估的最着名的语言."
但是,并不总是存储计算的值以便更快地访问(例如,考虑斐波纳契数的递归定义).我在#haskell问了一个人,答案是这个记忆是自动完成的"只在一个例子中,例如,如果你有'让foo = bar baz',foo将被评估一次".
我的问题是:实例究竟意味着什么,还有其他情况,而不是自动完成记忆的情况?
将此行为描述为"memoization"具有误导性."打电话的需要 "只是意味着一个给定的输入功能将0和1之间的某个地方次评估,从来没有超过一次.(也可以部分评估,这意味着函数只需要输入的一部分.)相比之下,"按名称调用"只是表达式替换,这意味着如果将表达式2 + 3作为函数的输入,它可能如果输入被多次使用,则被多次评估.按需调用和按名称调用都是非严格的:如果未使用输入,则永远不会对其进行求值.大多数编程语言都是严格的,并且使用"按值调用"方法,这意味着在开始评估函数之前评估所有输入,无论是否使用输入.这一切都与let表达式无关.
Haskell不执行任何自动memoization.让表达式不是memoization的一个例子.但是,大多数编译器将以按需调用的方式评估let绑定.如果将let表达式建模为函数,则"按需调用"心态确实适用:
let foo = expression one in expression two that uses foo
==>
(\foo -> expression two that uses foo) (expression one)
Run Code Online (Sandbox Code Playgroud)
这不能正确地模拟递归绑定,但是你明白了.
haskell语言定义不定义调用代码的时间或频率.无限循环是根据"底部"(书面⊥)定义的,它是一个表示错误条件的值(存在于所有类型中).只要程序(以及错误条件的存在/不存在,包括无限循环!)根据规范行事,编译器就可以自由决定何时以及多久评估一次.
也就是说,通常的做法是大多数表达式生成"thunks" - 基本上是指向某些代码和一些上下文数据的指针.第一次尝试检查表达式的结果(即模式匹配)时,thunk是"强制"的; 执行指向的代码,并用真实数据覆盖thunk.这反过来可以递归地评估其他thunk.
当然,这样做总是很慢,所以编译器通常会尝试分析你最后是什么时候立即强迫thunk(即,当某个东西对所讨论的值有'严格'时),如果它找到了这,它将跳过整个thunk事件,并立即调用代码.如果它不能证明这一点,它仍然可以进行这种优化,只要它确保立即执行thunk不会崩溃或导致无限循环(或它以某种方式处理这些条件).