我无法理解seq
, 强制评估和$
, 之间的区别。
3 回答
($) :: (a -> b) -> a -> b
接受一个函数和一个值。它返回一个 thunk。当该 thunk 被强制执行时,它会产生将函数应用于该值的结果。
> let x = succ $ (2 :: Int)
> :sprint x
x = _
> x
3
> :sprint x
x = 3
($)
完全等同于普通函数应用程序,但运算符优先级较低,这对于避免一些括号很有用。
print (take 10 (map (* 2) (filter even [1..])))
print $ take 10 $ map (* 2) $ filter even [1..]
seq :: a -> b -> b
非常不同:它在结果和第一个参数之间安排了依赖关系,因此当强制结果时,首先评估第一个参数:
> let y = succ (1 :: Int)
> :sprint y
y = _
> let z = y `seq` (3 :: Int)
> :sprint z
z = _
> z
3
> :sprint z
z = 3
> :sprint y
y = 2
在这里,y
和z
是最初未评估的重击。但是评估z
也有评估的副作用,因为我们已经安排了对usingy
的依赖。您还可以使用from观察评估顺序:y
seq
trace
Debug.Trace
> import Debug.Trace
> (trace "a evaluated" ()) `seq` (trace "b evaluated" ())
a evaluated
b evaluated
()
> let p = (trace "a evaluated" (1 :: Int), trace "b evaluated" (2 :: Int))
> :sprint p
p = (_,_)
> snd p
b evaluated
2
> :sprint p
p = (_,2)
> fst p
a evaluated
1
> :sprint p
p = (1,2)
seq
是一个低级操作,主要用于性能原因,因为它允许您控制何时评估 thunk。例如,seq
在定义中使用,foldl'
以确保在进行下一步之前评估折叠的每个步骤的结果。它的惰性表亲foldl
不这样做,因此它经常会累积一系列深度嵌套的 thunk,这可能会在评估时导致堆栈溢出。
Haskell 默认是惰性求值的。因此,带有seq
's 签名 ( a -> b -> b
) 的“正常”函数将忽略其第一个参数,然后返回其第二个参数——它不能对任何一个参数做任何其他事情,因为它不知道它们是什么!
但是seq
有点特别,做的事情略有不同。相反,它严格评估其第一个参数,然后返回其第二个参数。这对于各种目的很有用,例如强制副作用的顺序,或防止在计算过程中积累大量的 thunk。你可以在这里找到更多信息:https ://wiki.haskell.org/Seq 。
正如@palik 所说,的类型签名$
是完全不同的,它做了一些不同的事情:它将一个函数应用于一个参数。与普通函数应用的区别在于$
它的运算符优先级很低,可以避免写很多括号。
seq
并($)
具有不同的类型签名:
λ> :t seq
seq :: a -> b -> b
λ> :t ($)
($) :: (a -> b) -> a -> b
λ> (+1) `seq` 2
2
λ> (+1) $ 2
3