Haskell: What is Weak Head Normal Form?
弱头正常形态(whnf)是什么意思?头部正常形态(hnf)和正常形态(nf)是什么意思?
现实世界哈斯克尔说:
The familiar seq function evaluates an expression to what we call head
normal form (abbreviated HNF). It stops once it reaches the outermost
constructor (the"head"). This is distinct from normal form (NF), in
which an expression is completely evaluated.You will also hear Haskell programmers refer to weak head normal form
(WHNF). For normal data, weak head normal form is the same as head
normal form. The difference only arises for functions, and is too
abstruse to concern us here.
号
我已经阅读了一些资源和定义(haskell wiki和haskell邮件列表和免费词典),但我不明白。有人能举个例子或者提供一个外行定义吗?
我想这应该类似于:
1 2 3 4 5 | WHNF = thunk : thunk HNF = 0 : thunk NF = 0 : 1 : 2 : 3 : [] |
我还是很困惑。我知道有些答案说忽略了HNF。从阅读各种定义来看,WHNF和HNF中的常规数据似乎没有区别。然而,在函数方面似乎确实存在差异。如果没有区别,为什么
另一个困惑点来自haskell wiki,它指出
Common newbie stack overflowing code:
People who understand seq and weak head normal form (whnf) can
immediately understand what goes wrong here. (acc+x, len+1) is already
in whnf, so seq, which reduces a value to whnf, does nothing to this.
This code will build up thunks just like the original foldl example,
they'll just be inside a tuple. The solution is just to force the
components of the tuple, e.g.
号
-StackOverflow上的haskell wiki
我尽量用简单的术语解释一下。正如其他人所指出的,头部正态形式不适用于haskell,所以在这里我不会考虑它。
正常形式正常形式的表达式是完全计算的,不能再进一步计算子表达式(即它不包含未计算的thunk)。
这些表达式都是正常形式:
1 2 3 | 42 (2,"hello") \x -> (x + 1) |
这些表达式不是正常形式:
1 2 3 4 | 1 + 2 -- we could evaluate this to 3 (\x -> x + 1) 2 -- we could apply the function "he" ++"llo" -- we could apply the (++) (1 + 1, 2 + 2) -- we could evaluate 1 + 1 and 2 + 2 |
号弱头正态
弱头标准形式的表达式已被计算为最外层的数据构造函数或lambda抽象(head)。子表达式可能已计算,也可能未计算。因此,每一个正常形式的表达也都是弱头正常形式,尽管一般情况下相反。
为了确定一个表达式是否为弱头正常形式,我们只需要查看表达式的最外层。如果它是一个数据构造函数或lambda,那么它处于弱头正常形式。如果它是一个函数应用程序,则不是。
这些表达式为弱头正规形式:
1 2 3 | (1 + 1, 2 + 2) -- the outermost part is the data constructor (,) \x -> 2 + 2 -- the outermost part is a lambda abstraction 'h' : ("e" ++"llo") -- the outermost part is the data constructor (:) |
如前所述,上面列出的所有正规形式表达式也都是弱头正规形式。
这些表达式不是弱头正规形式:
1 2 3 | 1 + 2 -- the outermost part here is an application of (+) (\x -> x + 1) 2 -- the outermost part is an application of (\x -> x + 1) "he" ++"llo" -- the outermost part is an application of (++) |
。堆栈溢出
将表达式计算为弱头标准形式可能需要先将其他表达式计算为WHNF。例如,要评估
当您构建一个大型表达式时会发生这种情况,该表达式在很大一部分数据构造函数或lambda被计算之前不会生成任何数据构造函数或lambda。这通常是由于使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | foldl (+) 0 [1, 2, 3, 4, 5, 6] = foldl (+) (0 + 1) [2, 3, 4, 5, 6] = foldl (+) ((0 + 1) + 2) [3, 4, 5, 6] = foldl (+) (((0 + 1) + 2) + 3) [4, 5, 6] = foldl (+) ((((0 + 1) + 2) + 3) + 4) [5, 6] = foldl (+) (((((0 + 1) + 2) + 3) + 4) + 5) [6] = foldl (+) ((((((0 + 1) + 2) + 3) + 4) + 5) + 6) [] = (((((0 + 1) + 2) + 3) + 4) + 5) + 6 = ((((1 + 2) + 3) + 4) + 5) + 6 = (((3 + 3) + 4) + 5) + 6 = ((6 + 4) + 5) + 6 = (10 + 5) + 6 = 15 + 6 = 21 |
请注意,在将表达式转换为弱头正常形式之前,它必须非常深入。
你可能会想,为什么哈斯克尔不提前减少内在表达?这是因为哈斯克尔的懒惰。由于一般不能假定每个子表达式都是必需的,因此表达式是从外到内计算的。
(GHC有一个严格的分析程序,它可以检测出某些情况下总是需要子表达式,然后可以提前对其进行评估。然而,这只是一个优化,您不应该依赖它来避免溢出)。
另一方面,这种表达是完全安全的:
1 2 3 |
。
为了避免在我们知道所有子表达式都必须计算时构建这些大型表达式,我们希望强制提前计算内部部分。
埃多克斯1〔3〕它是
。
每次迭代
1 2 3 4 5 6 7 8 |
但正如Haskellwiki上的示例所提到的,这并不能在所有情况下都保存您的信息,因为累加器只计算为WHNF。在本例中,累加器是一个元组,因此它只强制对元组构造函数进行评估,而不是强制
1 2 3 4 5 6 7 |
。
为了避免这种情况,我们必须这样做,以便对元组构造函数进行评估,从而强制对
1 2 3 4 5 6 7 8 9 |
haskell wikibooks中有关thunks和弱头正常形式的章节"懒惰的描述"提供了对whnf的非常好的描述以及这一有用的描述:
。
Evaluating the value (4, [1, 2]) step by step. The first stage is
completely unevaluated; all subsequent forms are in WHNF, and the last
one is also in normal form.
号
haskell程序是表达式,通过执行评估来运行。
要计算表达式,请将所有函数应用程序替换为其定义。这样做的顺序并不重要,但仍然很重要:从最外层的应用程序开始,从左到右进行;这称为惰性评估。
例子:
1 2 3 4 5 6 7 |
。
当没有更多的函数应用程序需要替换时,计算将停止。结果是正常形式(或简化正常形式,RNF)。无论以何种顺序计算表达式,最终都将使用相同的正常形式(但仅当计算终止时)。
对于懒惰的评估有一个稍微不同的描述。也就是说,你应该只评估弱智正常形态的一切。表达式在whnf中有三种情况:
- 施工单位:
constructor expression_1 expression_2 ... 。 - 参数太少的内置函数,如
(+) 2 或sqrt 。 - lambda表达式:
\x -> expression 。
换句话说,表达式的头(即最外面的函数应用程序)无法进一步计算,但函数参数可能包含未计算的表达式。
WHNF示例:
1 2 3 | 3 : take 2 [2,3,4] -- outermost function is a constructor (:) (3+1) : [4..] -- ditto \x -> 4+5 -- lambda expression |
笔记
在http://foldoc.org/weak+head+normal+form head-normal-form中给出了一个很好的示例解释,它甚至简化了函数抽象中表达式的位,而"weak"head-normal-form则停止函数抽象。
如果你有:
1 | \ x -> ((\ y -> y+x) 2) |
号
这是弱头正常形态,但不是头正常形态…因为可能的应用程序被卡在一个还无法评估的函数中。
实际的头部正常形态将难以有效实施。它需要在函数内部进行搜索。因此,弱头标准形式的优点是,您仍然可以将函数作为不透明类型来实现,因此它更兼容编译语言和优化。
whnf不希望对lambda体进行评估,因此
1 2 | WHNF = \a -> thunk HNF = \a -> a + c |
1 2 3 |
。
计算结果为
1 | \d e -> (\f -> 2 + 5 + d + e + f) 2 |
而不是,什么会使用hnf
1 | \d e -> 2 + 5 + d + e + 2 |
。
基本上,假设您有某种类型的thunk,
现在,如果我们想将
1 |