2

我无法理解seq, 强制评估和$, 之间的区别。

4

3 回答 3

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

在这里,yz是最初未评估的重击。但是评估z也有评估的副作用,因为我们已经安排了对usingy的依赖。您还可以使用from观察评估顺序:yseqtraceDebug.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,这可能会在评估时导致堆栈溢出。

于 2017-06-23T16:09:47.527 回答
3

Haskell 默认是惰性求值的。因此,带有seq's 签名 ( a -> b -> b) 的“正常”函数将忽略其第一个参数,然后返回其第二个参数——它不能对任何一个参数做任何其他事情,因为它不知道它们是什么!

但是seq有点特别,做的事情略有不同。相反,它严格评估其第一个参数,然后返回其第二个参数。这对于各种目的很有用,例如强制副作用的顺序,或防止在计算过程中积累大量的 thunk。你可以在这里找到更多信息:https ://wiki.haskell.org/Seq 。

正如@palik 所说,的类型签名$是完全不同的,它做了一些不同的事情:它将一个函数应用于一个参数。与普通函数应用的区别在于$它的运算符优先级很低,可以避免写很多括号。

于 2017-06-23T15:55:39.377 回答
1

seq($)具有不同的类型签名:

λ> :t seq
seq :: a -> b -> b
λ> :t ($)
($) :: (a -> b) -> a -> b
λ> (+1) `seq` 2
2
λ> (+1) $ 2
3
于 2017-06-23T14:55:22.007 回答