【问题标题】:the seq function and strictnessseq 函数和严格性
【发布时间】:2012-06-18 06:46:57
【问题描述】:

我一直在想这个问题,但我一直没有找到任何关于它的信息。

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

我发现很难了解此函数的功能。

【问题讨论】:

标签: haskell strict


【解决方案1】:

你并不孤单。 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),所以在fooqbar 的结果之前被评估。

这也是它不起作用的原因:

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 与函数相关的一些后果。

【讨论】:

  • 只是为了明确一点:关于 when seq 应该是函数的无操作没有争议。当函数“有一个 lambda 显示”时,它是一个无操作,而不是在 lambda 处于最顶层之前必须完成一些计算(因此准备好应用)时,它不是一个无操作。争议在于我们是否应该在设计优化时考虑使用seq——因为seq在Haskell的语义中占据了一个不舒服的空间。
  • 您的回答表明,seq a b 首先评估 a,但本文后面的内容不一定正确。因此pseq 存在。 wiki.haskell.org/Seq “但是,在这种情况下,先评估 b 然后再评估 a,然后返回 b 是完全合法的做法;发明 pseq 是为了防止这种歧义,但那是另一回事了。”
  • @NiklasPeter 你说得对,seq a b 不保证两者之间的顺序,而是保证如果a 发散,那么seq a b 也会发散。我这样写答案是因为我认为它更容易理解,但也许有更好的方法来做到这一点,既清晰又准确。
  • 如果seq 没有实际排序,只是要求当右手边是WHNF 或更多时,那么左手边也必须是WHNF,那么是否经常出现右手边实际上是先评估的? wiki.haskell.org/Seq 这里的页面说“实际上,这几乎不会发生......”。
  • 我从来没有真正检查过它有多普遍。 AIUI通常发生的方式是编译器根据RHS是否发散生成不同的分支,或者可以静态确定它确实发散,在这种情况下,LHS可能根本不被评估。这里经常涉及异常,因为它们被认为是分歧。
【解决方案2】:

你可以把seq想象成:

seq a b = case a of
            _ -> b

这会将 a 评估为头部正常形式 (WHNF),然后继续评估 b

在 augustss 评论后编辑:这个case ... ofstrict, GHC Core one,它总是强制它的论点。

【讨论】:

  • 除非 Haskell 不计算 a
猜你喜欢
  • 2020-08-12
  • 1970-01-01
  • 1970-01-01
  • 2011-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
相关资源
最近更新 更多