seq功能和严格性

Und*_*ren 40 haskell strict

我一直在想这个,但我一直无法找到它.

使用该seq功能时,它如何真正起作用的?在任何地方,只是解释说seq a b评估a,丢弃结果并返回b.

但是,是什么真正意味着什么?以下结果将严格评估:

foo s t = seq q (bar q t) where
      q = s*t
Run Code Online (Sandbox Code Playgroud)

我的意思是,q在使用之前是否经过严格评估bar?以下是相同的:

foo s t = seq (s*t) (bar (s*t) t)
Run Code Online (Sandbox Code Playgroud)

我觉得有点难以详细说明这个功能的功能.

Joh*_*n L 34

你不是一个人. seq由于一些不同的原因,它可能是最难以正确使用的Haskell函数之一.在你的第一个例子中:

foo s t = seq q (bar q t) where
      q = s*t
Run Code Online (Sandbox Code Playgroud)

q在评估之前bar q t进行评估.如果bar q t永远q不会被评估,也不会.所以,如果你有

main = do
    let val = foo 10 20
    return ()
Run Code Online (Sandbox Code Playgroud)

因为val从未使用,也不会进行评估.因此q也不会被评估.如果你有

main = print (foo 10 20)
Run Code Online (Sandbox Code Playgroud)

foo 10 20评估结果(by print),因此在foo q结果之前评估bar.

这也是为什么这不起作用的原因:

myseq x = seq x x
Run Code Online (Sandbox Code Playgroud)

从语义上讲,这意味着第一个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)
Run Code Online (Sandbox Code Playgroud)

xs必须是一个清单,因为map.在seq评估它时,它将基本上将代码转换为

case xs of
  [] -> map (*y) xs
  (_:_) -> map (*y) xs
Run Code Online (Sandbox Code Playgroud)

这意味着它将确定列表是否为空,然后返回第二个参数.请注意,不会评估任何列表值.所以你可以这样做:

Prelude> seq [undefined] 4
4
Run Code Online (Sandbox Code Playgroud)

但不是这个

Prelude> seq undefined 5
*** Exception: Prelude.undefined
Run Code Online (Sandbox Code Playgroud)

无论你使用什么数据类型作为seq第一个参数,对WHNF的评估将足以找出构造函数,而不是进一步.除非数据类型具有使用爆炸模式标记为严格的组件.然后,所有严格的字段也将被评估为WHNF.

编辑:(感谢Daniel Wagner在评论中的建议)

对于函数,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
Run Code Online (Sandbox Code Playgroud)

如果您将lambda绑定视为(内置)数据构造函数,seq则函数与在数据上使用它完全一致.

此外,"lambda绑定"包含所有类型的函数定义,无论是由lambda表示法还是作为普通函数定义.

HaskellWiki的seq页面的Controversy部分有一些关于seq函数的一些后果.

  • 只是为了说清楚:没有关于_when_`seq`应该是函数的无操作的争议.当函数"有一个lambda显示"时,这是一个无操作,而在一个lambda最顶层(因此准备好应用程序)之前必须进行某些计算时,它不是无操作.争论的焦点是我们是否应该在设计优化时考虑使用`seq` - 因为`seq`在Haskell的语义中占据了一个令人不舒服的空间. (3认同)
  • 您的答案表明,``seq ab``首先会评估“ a”,但这在本文之后并不一定是正确的。因此``pseq''存在。https://wiki.haskell.org/Seq“但是,先评估b然后再评估a,然后返回b是完全合法的事情;这是为了防止pseq被发明出来的这种歧义,但这是另一个故事。” (2认同)

gfo*_*our 5

你可以认为seq是:

seq a b = case a of
            _ -> b
Run Code Online (Sandbox Code Playgroud)

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

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

  • 除了 Haskell 不计算 `a`。 (2认同)