你不是一个人。 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
的输出之前进行计算,但它可能与s*t
的第一个参数不同bar
。如果编译器执行公共子表达式消除,它可能会公共化两个相同的表达式。不过,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
无论您对seq
第一个参数使用什么数据类型,对 WHNF 求值都足以找出构造函数,而无需进一步。除非该数据类型具有用 bang 模式标记为严格的组件。然后所有严格的字段也将被评估为 WHNF。
编辑:(感谢丹尼尔瓦格纳在评论中的建议)
对于函数,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 页面的争议部分有一些关于seq
函数的一些后果。