我一直在想这个问题,但我一直无法找到它。
使用seq
功能时,真正的工作原理是什么?在任何地方,只是解释说seq a b
评估a
,丢弃结果并返回b
。
但真正意味着什么?以下结果将得到严格评估:
foo s t = seq q (bar q t) where
q = s*t
我的意思是,q
在bar
中使用之前是否经过严格评估?以下是相同的:
foo s t = seq (s*t) (bar (s*t) t)
我觉得有点难以详细说明这个功能的功能。
答案 0 :(得分:34)
你并不孤单。由于几个不同的原因,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
输出之前进行评估,但它可能与bar q t = q*t
的第一个参数不同s*t
。如果编译器执行公共子表达式消除,则它可以共同使用两个相同的表达式。 GHC对CSE的位置可能相当保守,所以你不能依赖于此。如果我定义seq
,它会执行CSE并在使用该值之前评估baz xs y = seq xs (map (*y) xs)
。对于更复杂的表达式,它可能不会这样做。
您可能还想了解严格评估的含义。 xs
计算弱头正规形式(WHNF)的第一个参数,对于数据类型,它意味着解包最外层的构造函数。考虑一下:
map
由于seq
, case xs of
[] -> map (*y) xs
(_:_) -> map (*y) xs
必须是列表。当Prelude> seq [undefined] 4
4
对其进行评估时,它将基本上将代码转换为
Prelude> seq undefined 5
*** Exception: Prelude.undefined
这意味着它将确定列表是否为空,然后返回第二个参数。请注意,没有评估列表值。所以你可以这样做:
seq
但不是这个
seq
无论你使用-- 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
的第一个参数的数据类型是什么,对WHNF的评估将足以找出构造函数而不会进一步。除非数据类型具有使用爆炸模式标记为严格的组件。然后,所有严格的字段也将被评估为WHNF。
编辑:(感谢Daniel Wagner在评论中提出建议)
对于函数,seq
将评估表达式,直到函数“显示lambda”,这意味着它已准备好应用程序。以下是一些可能证明这意味着什么的例子:
seq
如果您将lambda绑定视为(内置)数据构造函数,函数上的{{1}}与在数据上使用它完全一致。
此外,“lambda绑定”包含所有类型的函数定义,无论是由lambda表示法还是作为普通函数定义。
HaskellWiki的seq页面的Controversy部分有一些关于{{1}}与函数相关的一些后果。
答案 1 :(得分:4)
您可以将seq
视为:
seq a b = case a of
_ -> b
这会将a
评估为正常格式(WHNF),然后继续评估b
。
在奥古斯特评论之后进行修改:此case ... of
是strict, GHC Core one,它总是强制论证。