你并不孤单。 seq 可能是最难正确使用的 Haskell 函数之一,原因有几个。在您的第一个示例中:
foo s t = seq q (bar q t) where
q = s*t
q 在评估 bar q t 之前评估。如果 bar q t 从未被评估,q 也不会被评估。所以如果你有
main = do
let val = foo 10 20
return ()
因为val 从未使用过,所以不会被评估。所以q 也不会被评估。如果你有
main = print (foo 10 20)
foo 10 20 的结果被评估(由print),所以在foo 内q 在bar 的结果之前被评估。
这也是它不起作用的原因:
myseq x = seq x x
从语义上讲,这意味着第一个 x 将在第二个 x 被评估之前被评估。但是如果第二个x 从未被评估过,那么第一个也不需要。所以seq x x 完全等同于x。
您的第二个示例可能是也可能不是同一件事。在这里,表达式s*t 将在bar 的输出之前计算,但它可能与bar 的第一个参数s*t 不同。如果编译器执行公共子表达式消除,它可能会公共化两个相同的表达式。不过,GHC 对于 CSE 的执行位置可能相当保守,因此您不能依赖这一点。如果我定义bar q t = q*t,它会执行 CSE 并评估s*t,然后在 bar 中使用该值。对于更复杂的表达式,它可能不会这样做。
您可能还想知道严格评估是什么意思。 seq 计算弱头范式 (WHNF) 的第一个参数,这对于数据类型意味着解包最外层的构造函数。考虑一下:
baz xs y = seq xs (map (*y) xs)
xs 必须是一个列表,因为map。当seq 评估它时,它本质上会将代码转换为
case xs of
[] -> map (*y) xs
(_:_) -> map (*y) xs
这意味着它将确定列表是否为空,然后返回第二个参数。请注意,没有评估任何列表值。所以你可以这样做:
Prelude> seq [undefined] 4
4
但不是这个
Prelude> seq undefined 5
*** Exception: Prelude.undefined
无论您为seqs 的第一个参数使用什么数据类型,对 WHNF 求值都足以找出构造函数,而无需进一步。除非该数据类型具有用 bang 模式标记为严格的组件。然后所有严格的字段也将被评估为 WHNF。
编辑:(感谢 Daniel Wagner 在 cmets 中的建议)
对于函数,seq 将评估表达式,直到函数“显示 lambda”,这意味着它已准备好应用。这里有一些例子可以说明这意味着什么:
-- ok, lambda is outermost
Prelude> seq (\x -> undefined) 'a'
'a'
-- not ok. Because of the inner seq, `undefined` must be evaluated before
-- the lambda is showing
Prelude> seq (seq undefined (\x -> x)) 'b'
*** Exception: Prelude.undefined
如果您将 lambda 绑定视为(内置)数据构造函数,seq 在函数上与在数据上使用它完全一致。
此外,“lambda 绑定”包含所有类型的函数定义,无论是由 lambda 表示法定义还是作为普通函数定义。
HaskellWiki 的 seq 页面的 Controversy 部分介绍了 seq 与函数相关的一些后果。