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

data MidiMessage = MidiMessage !Int !MidiMessage

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

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存储在那个内存位置。

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

如果你启用了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

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

> second (Foo undefined 1)
1

但是strict实参不能是未定义的,即使我们不使用值:

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