45

我一直想知道这个很多,但我一直无法找到任何关于它的东西。

使用该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)

我发现很难获得有关此功能的功能的详细信息。

4

2 回答 2

41

你不是一个人。 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函数的一些后果。

于 2012-06-15T09:27:38.113 回答
6

你可以认为seq是:

seq a b = case a of
            _ -> b

这将评估a为 head-normal form (WHNF),然后继续评估b

在 augustss 评论后编辑:case ... of严格的 GHC Core one,它总是强制它的论点。

于 2012-06-15T12:05:17.880 回答