什么是弱头范式?

发布于 2024-11-27 08:15:36 字数 1961 浏览 3 评论 0原文

弱头范式 (WHNF) 是什么意思? Head Normal form (HNF) 和 Normal Form (NF) 是什么意思?

现实世界 Haskell 指出:

熟悉的seq函数将表达式计算为我们想要的值 称为头范式 (缩写为HNF)。一旦到达就停止 最外面的构造函数(“头”)。这与正常情况不同 形式 (NF),其中表达式被完全求值。

您还会听到 Haskell 程序员提到  头部正常 形式(WHNF)。对于普通数据,弱头范式与 头部正常形态。差异仅出现在功能上,而且差异太大 在这里关心我们是很深奥的。

我已经阅读了一些资源和定义(Haskell WikiHaskell 邮件列表免费词典),但我不明白。有人可以举个例子或者提供一个外行定义吗?

我猜它类似于:

WHNF = thunk : thunk

HNF = 0 : thunk 

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

How do seq and ($!) related to WHNF and HNF?

更新

我还是很困惑。我知道有些答案说忽略 HNF。从阅读各种定义来看,WHNF 和 HNF 中的常规数据似乎没有区别。然而,在功能方面似乎确实存在差异。如果没有区别,为什么 foldl' 需要 seq

另一个令人困惑的地方来自 Haskell Wiki,它指出 seq 简化为 WHNF,并且对以下示例没有任何作用。然后他们说必须使用 seq 来强制评估。这不是逼HNF吗?

常见新手堆栈溢出代码:

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

了解 seq 和弱头范式 (whnf) 的人可以立即明白这里出了什么问题。 (acc+x, len+1) 已经在 whnf 中,因此,seq(在 foldl' 的定义中)将值减少为 whnf,对 这。此代码将像原始的 foldl 示例一样构建 thunk,它们只是位于元组内。解决办法就是强制 元组的组成部分,例如

myAverage = uncurry (/) 。折叠' 
          (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)

-Stackoverflow 上的 Haskell Wiki

What does Weak Head Normal Form (WHNF) mean? What does Head Normal form (HNF) and Normal Form (NF) mean?

Real World Haskell states:

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.

I have read a few resources and definitions (Haskell Wiki and Haskell Mail List and Free Dictionary) but I don't get it. 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?

Update

I am still confused. I know some of the answers say to ignore HNF. From reading the various definitions it seems that there is no difference between regular data in WHNF and 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'?

Another point of confusion is from the Haskell Wiki, which states that seq reduces to WHNF, and will do nothing to the following example. Then they say that they have to use seq to force the evaluation. Is that not forcing it to 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. (acc+x, len+1) is already in whnf, so the seq (in the definition of foldl'), 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.

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

-Haskell Wiki on Stackoverflow

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

野侃 2024-12-04 08:15:36

我将尝试用简单的术语给出解释。正如其他人指出的那样,head 范式不适用于 Haskell,所以我不会在这里考虑它。

正常形式

正常形式的表达式被完全求值,并且不能进一步求值任何子表达式(即它不包含未求值的 thunk)。

这些表达式都是正规形式:

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

这些表达式不是正规形式:

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 + 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                -- 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。例如,要计算 WHNF 的 1 + (2 + 3),我们首先必须计算 2 + 3。如果计算单个表达式导致过多的嵌套计算,则结果是堆栈溢出。

当您构建一个大型表达式时,在计算大部分表达式之前,该表达式不会生成任何数据构造函数或 lambda,就会发生这种情况。这些通常是由 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

请注意,它必须非常深入才能使表达式变为弱头正常形式。

你可能会想,为什么 Haskell 不提前减少内部表达式呢?那是因为 Haskell 的懒惰。由于一般不能假设每个子表达式都需要,因此表达式是从外到内计算的。

(GHC 有一个严格分析器,可以检测某些总是需要子表达式的情况,然后提前计算它。这然而,这只是一种优化,您不应该依赖它来避免溢出)。

另一方面,这种表达式是完全安全的:

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. 

为了避免在我们知道必须计算所有子表达式时构建这些大型表达式,我们希望强制提前计算内部部分。

seq

seq 是一个特殊函数,用于强制计算表达式。它的语义是,seq x y 意味着每当 y 被评估为弱头范式时,x 也被评估为弱头范式。

它是在 foldl' 的定义中使用的地方,它是 foldl 的严格变体。

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

foldl' 的每次迭代都会强制累加器达到 WHNF。因此,它避免构建大型表达式,从而避免堆栈溢出。

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.

但正如 HaskellWiki 上的示例所提到的,这并不能在所有情况下都拯救你,因为累加器仅评估为 WHNF。在下面的示例中,累加器是一个元组,因此它只会强制评估元组构造函数,而不是 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.

为了避免这种情况,我们必须使得对元组构造函数的求值强制对 acclen 求值。我们通过使用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.

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.

Normal form

An expression in normal form is fully evaluated, and no sub-expression could be evaluated any further (i.e. it contains no un-evaluated thunks).

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). 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. 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. For example, to evaluate 1 + (2 + 3) to WHNF, we first have to evaluate 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. These are often caused by this kind of usage of 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? That is because of Haskell's laziness. 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).

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. Its semantics are that seq x y means that whenever y is evaluated to weak head normal form, x is also evaluated to weak head normal form.

It is among other places used in the definition of foldl', the strict variant of 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. 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. In the example below, the accumulator is a tuple, so it will only force evaluation of the tuple constructor, and not acc or len.

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. We do this by using 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.
梦巷 2024-12-04 08:15:36

Haskell Wikibooks 中关于Thunks 和弱头范式的部分 对懒惰的描述提供了非常好的描述WHNF 以及这个有用的描述:

逐步评估值 (4, [1, 2])。第一阶段是完全未评估;所有后续形式均为 WHNF,最后一个也是正常形式。

逐步评估值 (4, [1, 2])。第一阶段是
完全没有被评估;所有后续形式均采用 WHNF 形式,最后一个形式
一个也处于正常状态。

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:

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.

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.

云仙小弟 2024-12-04 08:15:36

Haskell 程序是表达式,它们通过执行求值来运行。

要计算表达式,请将所有函数应用程序替换为其定义。执行此操作的顺序并不重要,但仍然很重要:从最外面的应用程序开始,从左到右进行;这称为惰性求值

示例:

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

当没有更多功能应用程序可供替换时,评估停止。结果采用正规形式(或简化正规形式,RNF)。无论您以何种顺序计算表达式,您总是会得到相同的范式(但前提是计算终止)。

对于惰性求值的描述略有不同。也就是说,它表示您应该仅将所有内容评估为弱头正常形式。表达式处于 WHNF 中的情况正好有三种情况:

  • 构造函数:constructor expression_1 expression_2 ...
  • 参数太少的内置函数,例如 (+) 2sqrt
  • lambda 表达式:\x ->表达式

换句话说,表达式的头部(即最外层函数应用程序)不能被进一步求值,但函数参数可能包含未求值的表达式。

WHNF 示例:

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

注释

  1. WHNF 中的“头”不是指列表的头,而是指最外层的函数应用程序。
  2. 有时,人们将未评估的表达式称为“thunks”,但我认为这不是理解它的好方法。
  3. Head 范式 (HNF) 与 Haskell 无关。它与 WHNF 的不同之处在于 lambda 表达式的主体也在某种程度上进行求值。

Haskell programs are expressions and they are run by performing evaluation.

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

  • A constructor: constructor expression_1 expression_2 ...
  • A built-in function with too few arguments, like (+) 2 or sqrt
  • A lambda-expression: \x -> expression

In other words, the head of the expression (i.e. the outermost function application) cannot be evaluated any further, but the function argument may contain unevaluated expressions.

Examples of 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.
  2. Sometimes, people call unevaluated expressions "thunks", but I don't think that's a good way to understand it.
  3. Head normal form (HNF) is irrelevant for Haskell. It differs from WHNF in that the bodies of lambda expressions are also evaluated to some extent.
少女七分熟 2024-12-04 08:15:36

http://foldoc.org/Weak+Head+Normal+Form Head 给出了很好的解释和示例范式甚至简化了函数抽象内部表达式的位,而“弱”头范式则停止于函数抽象。

从源头来看,如果您有:

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

那是弱头部正常形式,但不是头部正常形式......因为可能的应用程序被困在尚无法评估的函数内部。

实际的头部范式很难有效地实现。这需要探索函数内部。因此,弱头范式的优点是您仍然可以将函数实现为不透明类型,因此它与编译语言和优化更兼容。

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.

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.

木格 2024-12-04 08:15:36

WHNF 不希望计算 lambda 的主体,因此

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

seq 希望其第一个参数位于 WHNF 中,因此

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

计算

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

结果为使用 HNF 的值

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

The WHNF does not want the body of lambdas to be evaluated, so

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

seq wants its first argument to be in WHNF, so

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

\d e -> 2 + 5 + d + e + 2
oО清风挽发oО 2024-12-04 08:15:36

基本上,假设您有某种 thunk,t

现在,如果我们想要将 t 计算为 WHNF 或 NHF(除了函数之外,它们是相同的),我们会发现我们得到类似

t1 : t2 的内容,其中 t1t2 是 thunk。在这种情况下,t1 将是您的 0(或者更确切地说,在没有额外拆箱的情况下,对 0 进行重击)

seq$! 计算 WHNF。注意

f $! x = seq x (f x)

Basically, suppose you have some sort of 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

t1 : t2 where t1 and t2 are thunks. In this case, t1 would be your 0 (or rather, a thunk to 0 given no extra unboxing)

seq and $! evalute WHNF. Note that

f $! x = seq x (f x)
芯好空 2024-12-04 08:15:36

我意识到这是一个老问题,但这里有 WHNF、HNF 和 NF 的明确数学定义。在纯 lambda 演算中:

  • 如果一项的形式为 NF,则该项为 NF

    <前><代码>λ x1。 λx2。 ... λ xn。 (x t1 t2 ... tm)

    其中 x 是变量,t1, t2, ..., tm 位于 NF 中。

  • 如果项的形式为 HNF,则该项属于 HNF

    <前><代码>λ x1。 λx2。 ... λ xn。 (x e1 e2 ... em)

    其中 x 是变量,e1, e2, ..., em 是任意项。

  • 如果一项是 lambda 项 λ x,则该项属于 WHNF。 e 表示任何术语 e 或者如果它的形式为

    x e1 e2 ... em
    

    其中 x 是变量,e1, e2, ..., em 是任意项。


现在考虑一种编程语言,其构造函数 a,b,c... 数量为 na, nb, nc...< /code>,这意味着只要 t1, t2, ..., tm 位于 NF 中,则术语 a t1 t2 ... tm 其中 m = na 是一个 redex,可以是评价。例如,Haskell 中的加法构造函数 + 具有元数 2,因为它仅在以正常形式给出两个参数时才进行计算(在本例中) case 整数,它们本身可以被视为无效构造函数)。

  • 如果项的形式为 NF,则该项属于 NF

    <前><代码>λ x1。 λx2。 ... λ xn。 (x t1 t2 ... tm)

    其中 x 是一个变量或一个 arity n 的构造函数,其中 m m n 。 nt1, t2, ..., tm 属于 NF。


  • 如果项的形式为 HNF,则该项属于 HNF

    <前><代码>λ x1。 λx2。 ... λ xn。 (x e1 e2 ... em)

    其中 x 是一个变量或一个 arity n 的构造函数,而 e1, e2, ... em 是任意项 < em>只要前 n 个参数并非全部都在 NF 中。

  • 如果满足以下条件,则项在 WHNF 中:它是 lambda 项 λ x。 e 表示任何术语 e 或者如果它的形式为

    x e1 e2 ... em
    

    其中 x 是一个变量或一个 arity n 的构造函数,而 e1, e2, ... em 是任意项 < em>只要前 n 个参数并非全部都在 NF 中。


特别是,NF 中的任何术语都是 HNF 中的,并且 HNF 中的任何术语都是属于 WHNF,但反之则不然。

I realize this is an old question, but here is an explicit mathematical definition of WHNF, HNF, and NF. In the pure lambda calculus:

  • A term is in NF if it is of the form

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

    where x is a variable, and t1, t2, ..., tm are in NF.

  • A term is in HNF if it is of the form

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

    where x is a variable, and e1, e2, ..., em are arbitrary terms.

  • A term is in WHNF if it is either a lambda term λ x. e for any term e or if it is of the form

    x e1 e2 ... em
    

    where x is a variable and e1, e2, ..., em are arbitrary terms.


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. 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).

  • A term is in NF if it is of the form

    λ 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.

  • A term is in HNF if it is of the form

    λ 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.

  • A term is in WHNF if it is either a lambda term λ x. e for any term e or if it is of the form

    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.


In particular, any term in NF is in HNF, and any term in HNF is in WHNF, but not conversely.

如若梦似彩虹 2024-12-04 08:15:36

在图简化的实现中,对 HNF 的惰性求值迫使您处理 lambda 演算的名称捕获问题,而对 WHNF 的惰性求值可以让您避免这种情况。

Simon Peyton Jones 的《函数式编程语言的实现》第 11 章

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.

This is explained in Chapter 11 of The Implementation of Functional Programming Languages by Simon Peyton Jones.

臻嫒无言 2024-12-04 08:15:36

Head 范式意味着没有 head redex

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

简化为:R,用于 redex(是的,其中还有另一个 redex,但这无关紧要)。这是一个 head redex,因为它是最左边的 redex(唯一的 redex),并且它之前没有 lambda 项(变量或 lambda 表达式(应用程序或抽象)),只有 0 到 n 个抽象器(如果 R 是 redex (λx.A)B R 的抽象器是 λx),在本例中为 0。

因为有一个头 redex,所以它不在 HNF 中,因此也不是在 NF 中,因为有一个 redex。

WHNF 意味着它是 lambda 抽象或 HNF 形式。上面的内容不在 HNF 中,也不是 lambda 抽象,而是一个应用程序,因此不在 WHNF 中。

λx.((λy.y+x)b)b 在 WHNF 中

是 lambda 抽象,但在 HNF 中不是,因为有一个头 λx.Rb

缩减为λx.((b+x)b)。没有 redex,因此它是正常形式。

考虑 λx.((λy.zyx)b),它简化为 λx.R,因此它不在 HNF 中。 λx.(k(λy.zyx)b) 简化为 λx.kR 因此它属于 HNF,而不属于 NF。

所有 NF 均位于 HNF 和 WHNF 中。所有 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). 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.

Because there's a head redex it is not in HNF and it is therefore also not in NF because there's a redex.

WHNF means that it is a lambda abstraction or in HNF. The above is not in HNF and it is not a lambda abstraction, but an application, and is therefore not in WHNF.

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

It is a lambda abstraction, but not in HNF because there is a head λx.Rb

Reduce to λx.((b+x)b). There is no redex therefore it is in normal form.

Consider λx.((λy.zyx)b), it simplifies to λx.R, so it is not in HNF. λx.(k(λy.zyx)b) simplifies to λx.kR therefore it is in HNF but not NF.

All NF are in HNF and WHNF. All HNF are WHNF.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文