Haskell的“seq”与其他函数有何不同?

时间:2015-10-10 14:34:23

标签: haskell lazy-evaluation

我对Haskell seqtutorial I'm reading中的工作方式的描述感到困惑。

教程说明了

  

评估表达式seq x y将首先评估x到WHNF,然后继续评估y

但早先在同一个教程中,在解释Haskell的懒惰评估如何起作用时,指出在评估函数时,争论是“评估,但只在必要时”,这意味着它的

  

参数将从左到右进行计算,直到它们的最顶层节点是与模式匹配的构造函数。如果模式是一个简单变量,则不评估参数;如果模式是构造函数,这意味着对WHNF的评估。

这种功能评估的描述一般来说与seq的描述没有什么不同。两者 - 在我初学者的阅读中 - 只是减少他们对WHNF的第一个论点。

这是对的吗? seq如何与任何其他Haskell函数不同 - 特别是它如何处理它的第一个参数?

2 个答案:

答案 0 :(得分:10)

如果没有seqevaluate,爆炸模式等,则适用以下规则:

  

所有评估都是通过模式匹配,if条件评估或原始数值运算直接驱动的。

事实证明,我们可以稍微眯一下,使所有看起来像模式匹配:

if c then x else y
=
case c of
  True -> x
  False -> y

x > y = case x of
          0 -> case y of ...

0 + y = y
1 + 1 = 2

等。最终,正在评估的事物是程序将采取的下一个原始IO动作,其他一切都只是通过模式匹配递归驱动。

从左到右的业务意味着,例如,

foo False 'd' = ...
foo True _ = ...

相当于

foo x y = case x of
            False -> case y of
                       'd' -> ...
            True -> ...

因此,如果将foo应用于True以及其他一些值,则不会强制该值,因为它会首先检查左侧模式。

seq,当应用于数据时,就像愚蠢的case一样。如果是x :: Bool,那么

x `seq` y = case x of
              True -> y
              False -> y

seq可以,相当狡猾地,也可以应用于函数,或者应用于未知类型的事物。除了通常的模式匹配链之外,它提供了一种强制评估的方法。

在Haskell的早期,seq是一个Seq类的方法,这很有道理。不幸的是,实施者发现,当更容易“欺骗”并让seq为所有事情工作时,必须处理该类是很烦人的。所以他们欺骗了,从那以后,程序分析和转换的某些方面变得更加困难。

答案 1 :(得分:2)

seq不仅在需要时立即评估其第一个参数 - 这与一般函数评估完全不同。

例如

let x = 1+1
    y = 2+2
in seq x (x, y)

立即评估表达式1+1但不评估2+2,即使两者都不需要立即评估。形象地说,返回的是

(2, 2+2)

不是(1+1, 2+2)

如果1+1代替1+2+3+...+1000000而不是trace这是一个相对便宜的计算但是它没有评估,懒惰的形式非常长并占用大量内存,这有时很有用,如果表达式评估得不够快,将有效地开始泄漏内存;这种情况在Haskell术语中称为空间泄漏

修改

为了解决您的评论,这里是一个虚假的场景,其中嵌套数据结构与各种深度模式匹配。数据结构中穿插了每个级别的import Debug.Trace (trace) data Age = Age Int data Name = Name String data Person = Person Name Age name = trace "!NAME!" $ Name $ trace "!NAME CONTENT!" $ "John " ++ "Doe" age = trace "!AGE!" $ Age $ trace "!AGE CONTENT!" $ 10 + 18 person = trace "!PERSON!" $ Person name age -- person = trace "!PERSON!" $ Person (trace "!n!" name) (trace "!a!" age) main = do case person of p -> print "hello" putStrLn "---" case person of Person name age -> print "hello" putStrLn "---" case person of Person (Name str) age -> print "hello" putStrLn "---" case person of Person (Name str) (Age i) -> print "hello" putStrLn "---" case person of Person (Name str) (Age i) -> putStrLn $ "hello: " ++ str putStrLn "---" case person of Person (Name str) (Age i) -> putStrLn $ "hello: " ++ show (str, i) 个调用,以便您可以监视它的评估方式:

"hello"
---
!PERSON!
"hello"
---
!NAME!
"hello"
---
!AGE!
"hello"
---
hello: !NAME CONTENT!
John Doe
---
hello: ("John Doe",!AGE CONTENT!
28)

<强>输出:

trace

请注意,putStrLn调用的输出“会干扰”print / Name调用的输出,但它实际上很好地展示了评估的发生方式在运行时。

此外,如果您使用Age而不是newtype定义datanewtype,则评估会与{{1}略有不同值没有运行时包装器,因此person的运行时内存表示将是一个“级别”更薄:

newtype Age = Age Int
newtype Name = Name String
data Person = Person Name Age
"hello"
---
!PERSON!
"hello"
---
"hello"
---
"hello"
---
hello: !NAME!
!NAME CONTENT!
John Doe
---
hello: ("John Doe",!AGE!
!AGE CONTENT!
28)