seq 运算符是
seq :: a -> b -> b
x
seq
y 将评估 x,足以检查它是否不是底部,然后丢弃结果并评估 y。这可能看起来没有用,但这意味着保证在考虑 y 之前对 x 进行评估。
这对 Haskell 来说非常好,但这是否意味着在
x `seq` f x
评估费用x
将支付两次(“丢弃结果”)?
seq 运算符是
seq :: a -> b -> b
x
seq
y 将评估 x,足以检查它是否不是底部,然后丢弃结果并评估 y。这可能看起来没有用,但这意味着保证在考虑 y 之前对 x 进行评估。
这对 Haskell 来说非常好,但这是否意味着在
x `seq` f x
评估费用x
将支付两次(“丢弃结果”)?
该seq
函数将丢弃 的值x
,但由于该值已被评估,所有对 的引用x
都“更新”为不再指向 的未x
评估版本,而是指向已评估的版本。因此,即使seq
评估和丢弃x
,该值也已为其他用户评估过x
,导致没有重复评估。
不,它不是计算和忘记,它是计算——它强制缓存。
例如,考虑以下代码:
let x = 1 + 1
in x + 1
由于 Haskell 是惰性的,因此计算结果为((1 + 1) + 1)
. 一个 thunk,包含一个 thunk 和 1 之和,内部 thunk 是一加一。
让我们使用非惰性语言 javascript 来展示它的样子:
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 仍然是惰性计算的)。
回到javascript:
function(){
var x = function(){ return 1 + 1 };
return (function(x){
return x + 1;
})( x() );
}
我相信x
只会被评估一次(并且结果会保留以供将来使用,这对于惰性操作来说是典型的)。这种行为是seq
有用的。
您可以随时检查unsafePerformIO
或trace
...</p>
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
seq x (f x)
f = id