感叹号在Haskell声明中意味着什么?

Dav*_*vid 246 syntax haskell lazy-evaluation

当我尝试使用真实项目来驱动Haskell时,我遇到了以下定义.我不明白每个论点前面的感叹号是什么意思,我的书似乎没有提到它.

data MidiMessage = MidiMessage !Int !MidiMessage
Run Code Online (Sandbox Code Playgroud)

Cur*_*son 295

这是严格的声明.基本上,这意味着在创建数据结构值时必须将其评估为所谓的"弱正常头部形式".让我们看一个例子,这样我们就可以看到这意味着什么:

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))
Run Code Online (Sandbox Code Playgroud)

f在评估时,上面的函数将返回一个"thunk":即执行以找出其值的代码.那时,Foo甚至还不存在,只是代码.

但在某些时候,有人可能会尝试查看它,可能是通过模式匹配:

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"
Run Code Online (Sandbox Code Playgroud)

这将执行足够的代码来完成它所需要的,而不是更多.所以它会创建一个带有四个参数的Foo(因为如果没有它,你就无法查看它).首先,因为我们正在测试它,我们需要一直评估4,我们发现它不匹配.

第二个不需要评估,因为我们没有测试它.因此,6我们只是存储代码以供以后评估,而不是存储在该内存位置(3+3).只有当有人看着它时,它才会变成6.

但是,第三个参数!位于其前面,因此严格评估:(4+4)执行,并8存储在该内存位置.

第四个参数也是严格评估的.但是这里有点棘手:我们的评估并不完全,只是对于正常的头部形态.这意味着我们弄清楚它是否NothingJust什么的,和商店,但我们就不再继续.这意味着我们不会存储,Just 10但实际上Just (5+5),将thunk保留在未评估状态.重要的是要知道,尽管我认为这样做的所有含义都超出了这个问题的范围.

如果启用BangPatterns语言扩展,则可以以相同的方式注释函数参数:

f x !y = x*y
Run Code Online (Sandbox Code Playgroud)

f (1+1) (2+2)将返回thunk (1+1)*4.

  • @David问题是评估价值的程度.弱头普通形式意味着:对它进行评估,直到到达最外层的构造函数.正常形式意味着评估整个值,直到没有未评估的组件被留下.由于Haskell允许各种级别的评估深度,因此它具有丰富的术语来描述这一点.您不倾向于在仅支持按值调用语义的语言中找到这些区别. (67认同)
  • 这非常有帮助.然而,我不禁想知道Haskell术语是否使人们用"弱正常头形","严格"等术语来理解事情变得更加复杂.如果我理解正确,听起来就像是!operator只是意味着存储表达式的求值,而不是存储一个匿名块以便稍后对其进行求值.这是一个合理的解释还是还有更多的东西? (14认同)
  • @David:我在这里写了一个更深入的解释:[Haskell:什么是弱头正常形式?](http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal-form/6889335#6889335).虽然我没有提到爆炸模式或严格注释,但它们相当于使用`seq`. (8认同)
  • 你好,天使,这实际上取决于编译器想要优化的程度。虽然我们用刘海来表示我们想将某些东西强行弱化为正常的头部形态,但我们没有(也不需要或想要)一种方式来表达“请确保您还没有评估这个重击”更进一步。” 从语义的角度来看,给定一个 thunk“n = 2 + 2”,您甚至无法判断对 *n* 的任何特定引用现在正在评估还是之前已评估。 (2认同)

Chr*_*way 83

查看strict和非严格构造函数参数之间区别的一种简单方法是它们在未定义时的行为方式.特定

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y
Run Code Online (Sandbox Code Playgroud)

由于非严格参数未被评估second,因此传入undefined不会导致问题:

> second (Foo undefined 1)
1
Run Code Online (Sandbox Code Playgroud)

但严格的论证不可能undefined,即使我们不使用该值:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined
Run Code Online (Sandbox Code Playgroud)

  • 这比 Curt Sampson 的答案要好,因为它实际上解释了“!”符号具有的用户可观察效果,​​而不是深入研究内部实现细节。 (5认同)

Chr*_*est 26

我相信这是一个严格的注释.

Haskell是一种纯粹而懒惰的函数式语言,但有时懒惰的开销可能过多或浪费.因此,为了解决这个问题,您可以要求编译器完全评估函数的参数,而不是解析thunk周围的问题.

此页面上有更多信息:Performance/Strictness.

  • 实际上,出于所有意图和目的,类型构造函数都是函数。您可以部分应用它们,并将它们传递给其他函数(例如,将map Just [1,2,3]映射为[Just 1,Just 2,Just 3]),依此类推。我发现考虑与他们进行模式匹配的能力以及完全无关的功能会有所帮助。 (2认同)