感叹号在Haskell声明中是什么意思?

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

data MidiMessage = MidiMessage !Int !MidiMessage
35462 次浏览

我认为这是一个严格注释。

Haskell是一种纯粹的懒惰的函数式语言,但有时懒惰的开销可能太大或浪费。为了解决这个问题,你可以要求编译器完全计算函数的参数,而不是解析。

本页有更多信息:性能/严格

这是一个严格的声明。基本上,这意味着在创建数据结构值时,必须将其计算为所谓的“弱头范式”。让我们看一个例子,这样我们就能明白这是什么意思:

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


f = Foo (2+2) (3+3) (4+4) (Just (5+5))

上面的函数f,在求值时,将返回一个“thunk”:也就是说,要执行的代码来计算它的值。此时,Foo甚至还不存在,只存在代码。

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

case f of
Foo 0 _ _ _ -> "first arg is zero"
_           -> "first arge is something else"

这将执行足够的代码来完成它所需要的,而不是更多。因此,它将创建一个带有四个参数的Foo(因为在它不存在的情况下,您无法查看它的内部)。首先,因为我们正在测试它,所以我们需要一直求值到4,在那里我们意识到它不匹配。

第二个不需要计算,因为我们没有测试它。因此,与其将6存储在该内存位置,不如将代码(3+3)存储在后面可能的计算中。只有当有人看它的时候,它才会变成6。

然而,第三个参数前面有一个!,因此被严格计算:(4+4)被执行,而8被存储在该内存位置。

对第四个参数进行了严格的计算。但这就有点棘手了:我们没有完全评估,而是只评估弱的正常头部形式。这意味着我们要弄清楚它是Nothing还是Just之类的东西,并存储它,但我们没有进一步的操作。这意味着我们存储的不是Just 10,而是Just (5+5),其中的thunk没有求值。知道这一点很重要,尽管我认为这一问题的所有含义都超出了这个问题的范围。

如果你启用了BangPatterns语言扩展名,你可以用同样的方式注释函数参数:

f x !y = x*y

f (1+1) (2+2)将返回坦克(1+1)*4

查看严格构造函数参数和非严格构造函数参数之间区别的一个简单方法是,它们在未定义时的行为。鉴于

data Foo = Foo Int !Int


first (Foo x _) = x
second (Foo _ y) = y

由于非严格参数不是由second计算的,因此传入undefined不会导致问题:

> second (Foo undefined 1)
1

但严格实参不能是undefined,即使我们不使用值:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined