Answers:
这是一个严格的声明。基本上,这意味着在创建数据结构值时,必须将其评估为所谓的“弱头范式”。让我们看一个例子,以便我们可以看到这意味着什么:
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)
保留其中的杂物未被评估。要知道这一点很重要,尽管我认为这的所有含义都超出了此问题的范围。
如果启用BangPatterns
语言扩展,则可以用相同的方式注释函数参数:
f x !y = x*y
f (1+1) (2+2)
会归还笨拙的(1+1)*4
。
seq
。
观察严格构造函数和非严格构造函数参数之间区别的一种简单方法是,当它们未定义时它们的行为。给定
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是一种纯粹且懒惰的函数式语言,但是有时懒惰的开销可能太多或浪费。因此,为了解决这个问题,您可以要求编译器完全评估函数的参数,而不是解析重击。
此页面上有更多信息:性能/严格度。
map Just [1,2,3]
获取[Just 1,Just 2,Just 3]),依此类推。我发现考虑与他们进行模式匹配的能力以及完全不相关的功能会有所帮助。