什么是弱头标准型(WHNF) ?Head Normal form (HNF)和Normal form (NF)是什么意思?

Real World Haskell声明:

我们熟悉的seq函数将表达式求值为 呼叫头标准型(简称HNF)。它一旦到达就会停止 最外层的构造函数(“head”)。这与正常情况不同 形式(NF),其中表达式被完全求值。 您还会听到Haskell程序员提到弱头正常 表单(WHNF)。对于正规数据,弱头正规形式与 头部正常形式。这种差异只出现在函数中,也是如此 这里涉及到我们的深奥。

我读了一些资源和定义(Haskell维基和Haskell邮件列表和免费字典),但我不明白。谁能举个例子或者给出一个外行的定义?

我猜它会类似于:

WHNF = thunk : thunk

HNF = 0 : thunk 

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

seq和($!)如何与WHNF和HNF相关?

更新

我还是很困惑。我知道有些答案说忽略HNF。从各种定义来看,WHNF和HNF中的常规数据似乎没有区别。然而,当涉及到函数时,似乎确实有区别。如果没有区别,为什么seq是必要的foldl'?

另一个混淆点来自Haskell Wiki,它声明seq简化为WHNF,并且不会对下面的示例执行任何操作。然后他们说他们必须使用seq来强制求值。这不是强迫它去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在Stackoverflow上


当前回答

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

Simon Peyton Jones的《函数式编程语言的实现》第11章对此进行了解释。

其他回答

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

从源头看,如果你有:

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

这是弱头部正常形式,但不是头部正常形式……因为可能的应用程序被卡在一个还不能求值的函数中。

实际的头部标准形式将难以有效地实现。这需要在函数内部进行操作。因此,弱头标准形式的优点是,您仍然可以将函数作为不透明类型来实现,因此它与编译语言和优化更加兼容。

基本上,假设你有某种坦克t。

现在,如果我们想求t的WHNF次方或NHF次方,它们除了函数外都是一样的,我们会发现我们会得到这样的东西

T1: t2,其中T1和t2是坦克。在这种情况下,t1将是你的0(或者更确切地说,在没有额外开箱的情况下,向0点击)

Seq和$!核定WHNF。请注意,

f $! x = seq x (f x)

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中有三种情况:

构造函数:构造函数expression_1 expression_2… 内置函数的参数太少,比如(+)2或平方根 一个lambda表达式:\x ->表达式

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

WHNF的例子:

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

笔记

WHNF中的“头”不是指列表的头,而是指最外层的函数应用程序。 有时,人们称未求值的表达式为“thunk”,但我不认为这是理解它的好方法。 头部标准型(HNF)与Haskell无关。它与WHNF的不同之处在于,lambda表达式的主体也在某种程度上被求值。

WHNF不希望对lambdas的主体进行计算,因此

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

Haskell Wikibooks对懒惰的描述中关于坦克和弱头正常形式的部分提供了对WHNF的非常好的描述,以及以下有用的描述:

逐步计算值(4,[1,2])。第一阶段是 完全未鉴定的;所有随后的形式都在WHNF中,而最后 一个也是正形式。