当我尝试使用真实项目来驱动Haskell时,我遇到了以下定义。我不明白每个论点前面的感叹号是什么意思,我的书似乎没有提到它。
data MidiMessage = MidiMessage !Int !MidiMessage
答案 0 :(得分:286)
这是严格的声明。基本上,这意味着在创建数据结构值时必须将其评估为所谓的“弱正常头部形式”。让我们看一个例子,以便我们可以看到这意味着什么:
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
。
答案 1 :(得分:78)
查看strict和non-strict构造函数参数之间区别的一种简单方法是它们在未定义时的行为方式。给定
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
答案 2 :(得分:24)
我认为这是严格的注释。
Haskell是一种纯粹的 lazy 函数式语言,但有时懒惰的开销可能太多或浪费。因此,为了解决这个问题,您可以要求编译器完全评估函数的参数,而不是解析周围的thunk。
此页面上有更多信息:Performance/Strictness。