这个常见问题解答说:
seq 操作符是
seq :: a -> b -> b
x
seq
y将评估x,仅检查它是否不是底部,然后丢弃结果并评估y。这可能看起来没有用,但它意味着在考虑y之前保证了评估x。x `seq` f x
评估
x
的成本会被支付两次(“丢弃结果”)吗?
这个常见问题解答说:
seq 操作符是
seq :: a -> b -> b
x
seq
y将评估x,仅检查它是否不是底部,然后丢弃结果并评估y。这可能看起来没有用,但它意味着在考虑y之前保证了评估x。x `seq` f x
评估
x
的成本会被支付两次(“丢弃结果”)吗?
seq
函数会丢弃x
的值,但是由于该值已经被求值,所有对x
的引用都被“更新”,不再指向未求值的版本,而是指向已求值的版本。因此,即使seq
评估和丢弃了x
,x
的其他用户也已经求值,从而不会出现重复的求值。
let x = 1 + 1
in x + 1
((1 + 1) + 1)
。一个 thunk 包含了一个 thunk 和一个数字的和,内部的 thunk 是 1 加上 1。 function(){
var x = function(){ return 1 + 1 };
return x() + 1;
}
像这样链接thunk可能会导致堆栈溢出,如果重复执行, 因此seq
来解救。
let x = 1 + 1
in x `seq` (x + 1)
(2+1)
时,但这几乎是真的——只是2的计算被强制在其他计算之前发生(但是2仍然是惰性计算)。 function(){
var x = function(){ return 1 + 1 };
return (function(x){
return x + 1;
})( x() );
}
x
只会被评估一次(并将结果保留以供将来使用,这是懒操作的典型行为)。这种行为使seq
变得有用。你可以随时使用 unsafePerformIO
或 trace
进行检查...
import System.IO.Unsafe (unsafePerformIO)
main = print (x `seq` f (x + x))
where
f = (+4)
x = unsafePerformIO $ print "Batman!" >> return 3
当然 seq
本身并不会“评估”任何内容。它只记录强制顺序依赖关系。强制本身是通过模式匹配触发的。当强制seq x(f x)
时,将首先强制x
(记忆结果值),然后将强制f x
。Haskell的惰性评估意味着它会记忆表达式强制的结果,因此不会执行重复的“评估”(在这里加上可怕的引号)。
我加了引号的“评估”,因为它意味着完全的评估。用Haskell wikibook的话来说,
"Haskell值高度分层;'评估' Haskell值可以意味着评估到其中任何一层。"
让我再强调一遍:seq
本身不会评估任何东西。在任何情况下,seq x x
都不会评估x
。当f=id
时,seq x (f x)
不会评估任何东西,这与报告所说的相反。
const
丢弃其第二个参数相同的方式丢弃结果。如果参数已被评估,它不会以某种方式重新评估它或丢弃结果,而是忽略它。“xseq
y 将评估 x,足以检查它不是底部,然后 忽略 结果并评估 y”可能是更好的表述方式。 - Tyler