当我尝试使用一个真实的项目来驱动它来学习 Haskell 时,我遇到了以下定义。我不明白每个论点前面的感叹号是什么意思,我的书似乎也没有提到它。
data MidiMessage = MidiMessage !Int !MidiMessage
当我尝试使用一个真实的项目来驱动它来学习 Haskell 时,我遇到了以下定义。我不明白每个论点前面的感叹号是什么意思,我的书似乎也没有提到它。
data MidiMessage = MidiMessage !Int !MidiMessage
这是一个严格的声明。基本上,这意味着在创建数据结构值时必须将其评估为所谓的“弱头范式”。让我们看一个例子,这样我们就能明白这意味着什么:
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)
将返回 thunk (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
我相信这是一个严格的注释。
Haskell 是一种纯粹且惰性的函数式语言,但有时惰性的开销可能过多或浪费。因此,为了解决这个问题,您可以要求编译器全面评估函数的参数,而不是解析 thunk。
此页面上有更多信息:Performance/Strictness。