Haskell 语言规范声明它是一种非严格语言,但没有关于评估策略(例如何时以及如何评估表达式,以及评估到什么级别)。在谈到模式匹配时,它确实多次提到了“评估”这个词。
我读过一篇关于惰性求值和弱头范式的精彩教程,但这只是一些编译器的实现策略,我在编写代码时不应该依赖它。
我来自严格的语言背景,如果我不了解我的代码是如何执行的,我就会感觉不对劲。我想知道为什么语言规范没有定义评估策略。
我希望有人能启发我。谢谢!
Haskell 语言规范声明它是一种非严格语言,但没有关于评估策略(例如何时以及如何评估表达式,以及评估到什么级别)。在谈到模式匹配时,它确实多次提到了“评估”这个词。
我读过一篇关于惰性求值和弱头范式的精彩教程,但这只是一些编译器的实现策略,我在编写代码时不应该依赖它。
我来自严格的语言背景,如果我不了解我的代码是如何执行的,我就会感觉不对劲。我想知道为什么语言规范没有定义评估策略。
我希望有人能启发我。谢谢!
我认为在 Haskell 中试图关心评估顺序会适得其反。不仅语言旨在使评估顺序无关紧要,而且评估顺序可以以奇怪和令人困惑的方式到处乱跳。此外,如果最终结果仍然相同,则该实现具有很大的自由来以不同的方式执行事情[1] 或大量重组您的程序[2],因此程序的不同部分可能会以不同的方式进行评估。
唯一真正的限制是您不能使用哪些评估策略。例如,您不能总是使用严格的评估,因为这会导致有效程序崩溃或进入无限循环。
const 17 undefined
take 10 (repeat 17)
也就是说,如果你真的在乎,你可以用来实现所有 Haskell 的一种有效策略是使用 thunk 进行惰性评估。每个值都表示在一个框中,该框包含该值或一个 thunk 子例程,当您最终需要使用该值时,该子例程可用于计算该值。
所以当你写
let xs = 1 : []
你会这样做:
x --> {thunk}
如果您从不检查 x 的内容,则 thunk 将保持未评估状态。但是,如果您曾经对 thunk 进行过一些模式匹配,那么您需要评估 thunk 以查看您采用的分支:
case xs of
[] -> ...
(y:ys) -> ...
现在,x 的 thunk 被评估,结果值存储在盒子中,以备不时之需。这是为了避免需要重新计算 thunk。请注意,在下图中,我Cons
用来代表:
列表构造函数
x ---> {Cons {thunk} {thunk}}
^ ^
| |
y ys
当然,仅存在模式数学是不够的,您首先需要首先被迫评估该模式匹配。最终,这归结为您的主要功能需要打印一个值或类似的东西。
需要指出的另一件事是,当我们第一次评估 Cons 构造函数时,我们没有立即评估它的内容。您可以通过运行不使用列表内容的程序来检查这一点:
length [undefined, undefined]
当然,当我们实际使用y
某个东西时,它对应的 thunk 就会被评估。
此外,您可以使用 bang 模式将构造函数字段标记为严格,以便在构造函数被评估时立即评估它们(我不记得是否需要为此打开扩展)
data LazyBox = LazyBox Int
data StrictBox = StrictBox !Int
case (LazyBox undefined) of (LazyBox _) -> 17 -- Gives 17
case (StrictBox undefined) of (StrictBox _) -> 17 -- *** Exception: Prelude.undefined
[1]:GHC 所做的一项重要优化是在严格分析器确定为严格的部分中使用严格评估。
[2]:最激进的例子之一是森林砍伐。