简体   繁体   English

什么是弱头范式?

[英]What is Weak Head Normal Form?

What does Weak Head Normal Form (WHNF) mean?弱头范式(WHNF) 是什么意思? What does Head Normal form (HNF) and Normal Form (NF) mean?头部范式(HNF) 和范式(NF) 是什么意思?

Real World Haskell states: 真实世界 Haskell指出:

The familiar seq function evaluates an expression to what we call head normal form (abbreviated HNF).熟悉的 seq 函数将表达式计算为我们所说的头部范式(缩写为 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.这与标准形式 (NF) 不同,在标准形式 (NF) 中,完全评估表达式。

You will also hear Haskell programmers refer to weak head normal form (WHNF).您还将听到 Haskell 程序员提到弱头范式 (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.区别只出现在函数上,太深奥了,我们这里不关心。

I have read a few resources and definitions ( Haskell Wiki and Haskell Mail List and Free Dictionary ) but I don't get it.我已经阅读了一些资源和定义( Haskell WikiHaskell 邮件列表免费词典),但我不明白。 Can someone perhaps give an example or provide a layman definition?有人可以举个例子或提供一个外行的定义吗?

I am guessing it would be similar to:我猜它会类似于:

WHNF = thunk : thunk

HNF = 0 : thunk 

NF = 0 : 1 : 2 : 3 : []

How do seq and ($!) relate to WHNF and HNF? seq($!)与 WHNF 和 HNF 有何关系?

Update更新

I am still confused.我还是很困惑。 I know some of the answers say to ignore HNF.我知道一些答案说忽略 HNF。 From reading the various definitions it seems that there is no difference between regular data in WHNF and HNF.从阅读各种定义来看,WHNF 和 HNF 中的常规数据似乎没有区别。 However, it does seem like there is a difference when it comes to a function.但是,在功能方面似乎确实有所不同。 If there was no difference, why is seq necessary for foldl' ?如果没有区别,为什么seq需要foldl'

Another point of confusion is from the Haskell Wiki, which states that seq reduces to WHNF, and will do nothing to the following example.另一个混淆点来自 Haskell Wiki,它指出seq简化为 WHNF,并且对以下示例没有任何作用。 Then they say that they have to use seq to force the evaluation.然后他们说他们必须使用seq来强制评估。 Is that not forcing it to HNF?这不是强迫它使用 HNF 吗?

Common newbie stack overflowing code:常见的新手堆栈溢出代码:

 myAverage = uncurry (/) . foldl' (\\(acc, len) x -> (acc+x, len+1)) (0,0)

People who understand seq and weak head normal form (whnf) can immediately understand what goes wrong here.了解 seq 和弱头范式 (whnf) 的人可以立即理解这里出了什么问题。 (acc+x, len+1) is already in whnf, so seq, which reduces a value to whnf, does nothing to this. (acc+x, len+1) 已经在 whnf 中,因此将值减少到 whnf 的 seq 对此没有任何作用。 This code will build up thunks just like the original foldl example, they'll just be inside a tuple.这段代码将像原始 foldl 示例一样构建 thunk,它们只是在一个元组中。 The solution is just to force the components of the tuple, eg解决方案只是强制元组的组件,例如

myAverage = uncurry (/) . foldl' (\\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)

- Haskell Wiki on Stackoverflow - Stackoverflow 上的 Haskell Wiki

I'll try to give an explanation in simple terms.我将尝试用简单的术语进行解释。 As others have pointed out, head normal form does not apply to Haskell, so I will not consider it here.正如其他人指出的那样,头部范式不适用于 Haskell,所以我不会在这里考虑它。

Normal form范式

An expression in normal form is fully evaluated, and no sub-expression could be evaluated any further (ie it contains no un-evaluated thunks).正常形式的表达式被完全评估,并且不能进一步评估任何子表达式(即它不包含未评估的 thunk)。

These expressions are all in normal form:这些表达式都是正常形式:

42
(2, "hello")
\x -> (x + 1)

These expressions are not in normal form:这些表达式不是正常形式:

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

Weak head normal form弱头范式

An expression in weak head normal form has been evaluated to the outermost data constructor or lambda abstraction (the head ).弱头部范式的表达式已被评估为最外层的数据构造函数或 lambda 抽象( head )。 Sub-expressions may or may not have been evaluated .子表达式可能会或可能不会被评估 Therefore, every normal form expression is also in weak head normal form, though the opposite does not hold in general.因此,每个范式表达式也是弱头范式,尽管相反的情况并不普遍。

To determine whether an expression is in weak head normal form, we only have to look at the outermost part of the expression.要确定一个表达式是否是弱头范式,我们只需要查看表达式的最外层部分。 If it's a data constructor or a lambda, it's in weak head normal form.如果它是数据构造函数或 lambda,则它是弱头范式。 If it's a function application, it's not.如果是函数应用程序,则不是。

These expressions are in weak head normal form:这些表达式是弱头范式:

(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 (:)

As mentioned, all the normal form expressions listed above are also in weak head normal form.如上所述,上面列出的所有范式表达式也是弱头范式。

These expressions are not in weak head normal form:这些表达式不是弱头范式:

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 (++)

Stack overflows堆栈溢出

Evaluating an expression to weak head normal form may require that other expressions be evaluated to WHNF first.将表达式计算为弱头范式可能需要首先将其他表达式计算为 WHNF。 For example, to evaluate 1 + (2 + 3) to WHNF, we first have to evaluate 2 + 3 .例如,要将1 + (2 + 3)评估为 WHNF,我们首先必须评估2 + 3 If evaluating a single expression leads to too many of these nested evaluations, the result is a stack overflow.如果对单个表达式求值导致这些嵌套求值过多,则结果是堆栈溢出。

This happens when you build up a large expression that does not produce any data constructors or lambdas until a large part of it has been evaluated.当您构建一个大型表达式时会发生这种情况,该表达式在评估大部分内容之前不会生成任何数据构造函数或 lambda。 These are often caused by this kind of usage of foldl :这些通常是由foldl这种用法引起的:

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

Notice how it has to go quite deep before it can get the expression into weak head normal form.请注意它在将表达式转换为弱头部范式之前必须非常深入。

You may wonder, why does not Haskell reduce the inner expressions ahead of time?你可能想知道,为什么 Haskell 不提前减少内部表达式? That is because of Haskell's laziness.那是因为 Haskell 的懒惰。 Since it cannot be assumed in general that every subexpression will be needed, expressions are evaluated from the outside in.由于通常不能假设每个子表达式都需要,因此表达式是从外向内求值的。

(GHC has a strictness analyzer that will detect some situations where a subexpression is always needed and it can then evaluate it ahead of time. This is only an optimization, however, and you should not rely on it to save you from overflows). (GHC 有一个严格分析器,它会检测一些总是需要子表达式的情况,然后它可以提前评估它。然而,这只是一种优化,你不应该依赖它来避免溢出)。

This kind of expression, on the other hand, is completely safe:另一方面,这种表达方式是完全安全的:

data List a = Cons a (List a) | Nil
foldr Cons Nil [1, 2, 3, 4, 5, 6]
 = Cons 1 (foldr Cons Nil [2, 3, 4, 5, 6])  -- Cons is a constructor, stop. 

To avoid building these large expressions when we know all the subexpressions will have to be evaluated, we want to force the inner parts to be evaluated ahead of time.为了避免在我们知道必须对所有子表达式进行求值时构建这些大型表达式,我们希望提前对内部部分进行求值。

seq

seq is a special function that is used to force expressions to be evaluated. seq是一个特殊的函数,用于强制计算表达式。 Its semantics are that seq xy means that whenever y is evaluated to weak head normal form, x is also evaluated to weak head normal form.它的语义是seq xy意味着每当y被评估为弱头范式时, x也被评估为弱头范式。

It is among other places used in the definition of foldl' , the strict variant of foldl .它是在定义中使用的其他地方foldl' ,严格变种foldl

foldl' f a []     = a
foldl' f a (x:xs) = let a' = f a x in a' `seq` foldl' f a' xs

Each iteration of foldl' forces the accumulator to WHNF. foldl'每次迭代都会强制累加器到 WHNF。 It therefore avoids building up a large expression, and it therefore avoids overflowing the stack.因此,它避免了构建大型表达式,从而避免堆栈溢出。

foldl' (+) 0 [1, 2, 3, 4, 5, 6]
 = foldl' (+) 1 [2, 3, 4, 5, 6]
 = foldl' (+) 3 [3, 4, 5, 6]
 = foldl' (+) 6 [4, 5, 6]
 = foldl' (+) 10 [5, 6]
 = foldl' (+) 15 [6]
 = foldl' (+) 21 []
 = 21                           -- 21 is a data constructor, stop.

But as the example on HaskellWiki mentions, this does not save you in all cases, as the accumulator is only evaluated to WHNF.但是正如 HaskellWiki 上的示例所提到的,这并不能在所有情况下都为您省钱,因为累加器仅评估为 WHNF。 In the example, the accumulator is a tuple, so it will only force evaluation of the tuple constructor, and not acc or len .在这个例子中,累加器是一个元组,所以它只会强制计算元组构造函数,而不是acclen

f (acc, len) x = (acc + x, len + 1)

foldl' f (0, 0) [1, 2, 3]
 = foldl' f (0 + 1, 0 + 1) [2, 3]
 = foldl' f ((0 + 1) + 2, (0 + 1) + 1) [3]
 = foldl' f (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1) []
 = (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1)  -- tuple constructor, stop.

To avoid this, we must make it so that evaluating the tuple constructor forces evaluation of acc and len .为了避免这种情况,我们必须使评估元组构造函数强制评估acclen We do this by using seq .我们通过使用seq做到这一点。

f' (acc, len) x = let acc' = acc + x
                      len' = len + 1
                  in  acc' `seq` len' `seq` (acc', len')

foldl' f' (0, 0) [1, 2, 3]
 = foldl' f' (1, 1) [2, 3]
 = foldl' f' (3, 2) [3]
 = foldl' f' (6, 3) []
 = (6, 3)                    -- tuple constructor, stop.

The section on Thunks and Weak Head Normal Form in the Haskell Wikibooks description of laziness provides a very good description of WHNF along with this helpful depiction: Haskell Wikibooks对懒惰的描述中关于 Thunks 和 Weak Head Normal Form的部分提供了对 WHNF 的非常好的描述以及这个有用的描述:

逐步评估值 (4, [1, 2])。第一阶段完全未评估;后面的所有形式都是WHNF,最后一个也是范式。

Evaluating the value (4, [1, 2]) step by step.逐步评估值 (4, [1, 2])。 The first stage is completely unevaluated;第一阶段完全未评估; all subsequent forms are in WHNF, and the last one is also in normal form.后面的所有形式都是WHNF,最后一个也是范式。

Haskell programs are expressions and they are run by performing evaluation . Haskell 程序是表达式,它们通过执行求值来运行。

To evaluate an expression, replace all function applications by their definitions.要对表达式求值,请将所有函数应用程序替换为其定义。 The order in which you do this does not matter much, but it's still important: start with the outermost application and proceed from left to right;执行此操作的顺序无关紧要,但仍然很重要:从最外层的应用程序开始,从左到右进行; this is called lazy evaluation .这称为惰性求值

Example:例子:

   take 1 (1:2:3:[])
=> { apply take }
   1 : take (1-1) (2:3:[])
=> { apply (-)  }
   1 : take 0 (2:3:[])
=> { apply take }
   1 : []

Evaluation stops when there are no more function applications left to replace.当没有更多的功能应用程序需要替换时,评估停止。 The result is in normal form (or reduced normal form , RNF).结果为范式(或简化范式,RNF)。 No matter in which order you evaluate an expression, you will always end up with the same normal form (but only if the evaluation terminates).无论您以哪种顺序对表达式求值,最终都会得到相同的范式(但前提是求值终止时)。

There is a slightly different description for lazy evaluation.惰性求值的描述略有不同。 Namely, it says that you should evaluate everything to weak head normal form only.也就是说,它说您应该仅将所有内容评估为弱头范式 There are precisely three cases for an expression to be in WHNF: WHNF 中的表达式恰好有三种情况:

  • A constructor: constructor expression_1 expression_2 ...一个构造函数: constructor expression_1 expression_2 ...
  • A built-in function with too few arguments, like (+) 2 or sqrt参数太少的内置函数,如(+) 2sqrt
  • A lambda-expression: \\x -> expression一个 lambda 表达式: \\x -> expression

In other words, the head of the expression (ie the outermost function application) cannot be evaluated any further, but the function argument may contain unevaluated expressions.换句话说,表达式的头部(即最外层的函数应用程序)不能被进一步评估,但函数参数可能包含未评估的表达式。

Examples of WHNF: WHNF的例子:

3 : take 2 [2,3,4]   -- outermost function is a constructor (:)
(3+1) : [4..]        -- ditto
\x -> 4+5            -- lambda expression

Notes笔记

  1. The "head" in WHNF does not refer to the head of a list, but to the outermost function application. WHNF 中的“头”不是指列表的头,而是指最外层的函数应用程序。
  2. Sometimes, people call unevaluated expressions "thunks", but I don't think that's a good way to understand it.有时,人们称未评估的表达式为“thunk”,但我认为这不是理解它的好方法。
  3. Head normal form (HNF) is irrelevant for Haskell.头部范式(HNF) 与 Haskell 无关。 It differs from WHNF in that the bodies of lambda expressions are also evaluated to some extent.它与 WHNF 的不同之处在于 lambda 表达式的主体也在某种程度上进行了评估。

A good explanation with examples is given at http://foldoc.org/Weak+Head+Normal+Form Head normal form simplifies even the bits of an expression inside of a function abstraction, while "weak" head normal form stops at function abstractions.http://foldoc.org/Weak+Head+Normal+Form给出了一个很好的例子解释 头部范式甚至简化了函数抽象内部的表达式的位,而“弱”头部范式在函数抽象处停止.

From the source, if you have:从源头来看,如果您有:

\ x -> ((\ y -> y+x) 2)

that is in weak head normal form, but not head normal form... because the possible application is stuck inside of a function that can't be evaluated yet.这是弱头部范式,但不是头部范式......因为可能的应用程序被困在一个无法评估的函数内。

Actual head normal form would be difficult to implement efficiently.实际的头部范式将难以有效实施。 It would require poking around inside of functions.它需要在函数内部四处探索。 So the advantage of weak head normal form is that you can still implement functions as an opaque type, and hence it's more compatible with compiled languages and optimization.所以弱头范式的优点是你仍然可以将函数实现为不透明类型,因此它更兼容编译语言和优化。

The WHNF does not want the body of lambdas to be evaluated, so WHNF 不想评估 lambdas 的主体,因此

WHNF = \a -> thunk
HNF = \a -> a + c

seq wants its first argument to be in WHNF, so seq希望它的第一个参数在 WHNF 中,所以

let a = \b c d e -> (\f -> b + c + d + e + f) b
    b = a 2
in seq b (b 5)

evaluates to评估为

\d e -> (\f -> 2 + 5 + d + e + f) 2

instead of, what would be using HNF而不是,什么会使用 HNF

\d e -> 2 + 5 + d + e + 2

Basically, suppose you have some sort of thunk, t .基本上,假设您有某种 thunk t

Now, if we want to evaluate t to WHNF or NHF, which are the same except for functions, we would find that we get something like现在,如果我们想将t评估为 WHNF 或 NHF,除了函数之外,它们是相同的,我们会发现我们得到类似

t1 : t2 where t1 and t2 are thunks. t1 : t2其中t1t2是 thunk。 In this case, t1 would be your 0 (or rather, a thunk to 0 given no extra unboxing)在这种情况下, t1将是您的0 (或者更确切地说,如果没有额外的拆箱,则为0

seq and $! seq$! evalute WHNF.评估 WHNF。 Note that注意

f $! x = seq x (f x)

In an implementation of graph reduction, lazy evaluation to HNF forces you to deal with the name capture problem of lambda calculus, whereas lazy evaluation to WHNF lets you avoid it.在图缩减的实现中,对 HNF 的惰性求值迫使您处理 lambda 演算的名称捕获问题,而对 WHNF 的惰性求值可以让您避免它。

This is explained in Chapter 11 of The Implementation of Functional Programming Languages by Simon Peyton Jones.这在 Simon Peyton Jones 的函数式编程语言的实现的第 11 章中进行了解释。

I realize this is an old question, but here is an explicit mathematical definition of WHNF, HNF, and NF.我意识到这是一个老问题,但这里是 WHNF、HNF 和 NF 的明确数学定义。 In the pure lambda calculus :纯 lambda 演算中

  • A term is in NF if it is of the form一个项在 NF 中,如果它是这样的形式

    λ x1. λ x2. ... λ xn. (x t1 t2 ... tm)

    where x is a variable, and t1, t2, ..., tm are in NF.其中x是一个变量, t1, t2, ..., tm在 NF 中。

  • A term is in HNF if it is of the form一个术语在 HNF 中,如果它是这样的形式

    λ x1. λ x2. ... λ xn. (x e1 e2 ... em)

    where x is a variable, and e1, e2, ..., em are arbitrary terms.其中x是一个变量, e1, e2, ..., em是任意项。

  • A term is in WHNF if it is either a lambda term λ x. e如果一个项是 lambda 项λ x. e ,则该项在 WHNF 中λ x. e λ x. e for any term e or if it is of the form λ x. e对于任何术语e或者如果它是这样的形式

    x e1 e2 ... em

    where x is a variable and e1, e2, ..., em are arbitrary terms.其中x是一个变量, e1, e2, ..., em是任意项。


Now consider a programming language with constructors a,b,c... of arity na, nb, nc... , which means that whenever t1, t2, ..., tm are in NF, then the term a t1 t2 ... tm where m = na is a redex and can be evaluated.现在考虑一种具有na, nb, nc...构造函数a,b,c...编程语言,这意味着只要t1, t2, ..., tm在 NF 中,那么术语a t1 t2 ... tm其中m = na是一个 redex 并且可以被评估。 For example, the addition constructor + in Haskell has arity 2 , because it only evaluates when it is given two arguments in normal form (in this case integers, which can themselves be considered as nullary constructors).例如,Haskell 中的加法构造函数+具有 arity 2 ,因为它仅在以标准形式给出两个参数时才进行计算(在这种情况下,整数,它们本身可以被视为空构造函数)。

  • A term is in NF if it is of the form一个项在 NF 中,如果它是这样的形式

    λ x1. λ x2. ... λ xn. (x t1 t2 ... tm)

    where x is either a variable or a constructor of arity n with m < n , and t1, t2, ..., tm are in NF.其中x是变量或具有m < n的元数n的构造函数,并且t1, t2, ..., tm在 NF 中。

  • A term is in HNF if it is of the form一个术语在 HNF 中,如果它是这样的形式

    λ x1. λ x2. ... λ xn. (x e1 e2 ... em)

    where x is either a variable or a constructor of arity n , and e1, e2, ... em are arbitrary terms so long as the first n arguments are not all in NF.其中xn的变量或构造函数,并且e1, e2, ... em是任意项,只要前n参数不是全部在 NF 中。

  • A term is in WHNF if it is either a lambda term λ x. e如果一个项是 lambda 项λ x. e ,则该项在 WHNF 中λ x. e λ x. e for any term e or if it is of the form λ x. e对于任何术语e或者如果它是这样的形式

    x e1 e2 ... em

    where x is either a variable or a constructor of arity n , and e1, e2, ... em are arbitrary terms so long as the first n arguments are not all in NF.其中xn的变量或构造函数,并且e1, e2, ... em是任意项,只要前n参数不是全部在 NF 中。


In particular, any term in NF is in HNF, and any term in HNF is in WHNF, but not conversely.特别地,NF 中的任何项都在 HNF 中,HNF 中的任何项都在 WHNF 中,但反之则不然。

Head normal form means there is no head redex头部范式意味着没有头部还原

(λx.((λy.y+x)b))b

Reduces to: R, for redex (and yes there's another redex inside it but this is irrelevant).减少到:R,对于 redex(是的,里面还有另一个 redex,但这无关紧要)。 This is a head redex because it's the leftmost redex (the only redex), and there are no lambda terms before it (variables or lambda expressions (applications or abstractions)), only 0 to n abstractors (if R is a redex (λx.A)B the abstractor of R is λx ), in this case 0.这是一个 head redex,因为它是最左边的 redex(唯一的 redex),并且在它之前没有 lambda 项(变量或 lambda 表达式(应用程序或抽象)),只有 0 到 n 个抽象器(如果 R 是一个 redex (λx.A)B R 的抽象器是λx ),在这种情况下是 0。

Because there's a head redex it is not in HNF and it is therefore also not in NF because there's a redex.因为有一个头部 redex,它不在 HNF 中,因此它也不在 NF 中,因为有一个 redex。

WHNF means that it is a lambda abstraction or in HNF. WHNF 意味着它是一个 lambda 抽象或在 HNF 中。 The above is not in HNF and it is not a lambda abstraction, but an application, and is therefore not in WHNF.以上不在 HNF 中,也不是 lambda 抽象,而是一个应用程序,因此不在 WHNF 中。

λx.((λy.y+x)b)b is in WHNF λx.((λy.y+x)b)b在 WHNF

It is a lambda abstraction, but not in HNF because there is a head λx.Rb它是一个 lambda 抽象,但不是在 HNF 中,因为有一个头部λx.Rb

Reduce to λx.((b+x)b) .减少到λx.((b+x)b) There is no redex therefore it is in normal form.没有 redex,因此它是正常形式。

Consider λx.((λy.zyx)b) , it simplifies to λx.R , so it is not in HNF.考虑λx.((λy.zyx)b) ,它简化为λx.R ,所以它不在 HNF 中。 λx.(k(λy.zyx)b) simplifies to λx.kR therefore it is in HNF but not NF. λx.(k(λy.zyx)b)简化为λx.kR因此它在 HNF 但不是 NF。

All NF are in HNF and WHNF.所有 NF 都在 HNF 和 WHNF 中。 All HNF are WHNF.所有 HNF 都是 WHNF。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM