【问题标题】:How is Haskell's 'seq' different from other functions?Haskell 的 'seq' 与其他函数有何不同?
【发布时间】:2016-01-08 09:32:25
【问题描述】:

我对 Haskell 的 seq 如何在 tutorial I'm reading 中工作的描述感到困惑。

教程指出

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

但在前面的同一篇教程中,在解释 Haskell 的惰性求值一般如何工作时,指出在求值函数时,arguments 是“求值的,但仅在必要时”,这意味着它的

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

一般来说,这个函数评估的描述似乎与seq 的描述没有什么不同。两者——在我的初学者阅读中——只是将他们的第一个论点简化为 WHNF。

正确吗? seq 与任何其他 Haskell 函数有何不同——特别是它处理第一个参数的方式?

【问题讨论】:

  • @Heinrich Apfelmus:关于laziness tutorial 的问题。
  • 嗯...一个说“除非需要,否则不会评估该参数”,另一个具体说“这将被评估为 WHNF”。你觉得这在什么方面是一样的?
  • @Cubic:当然,在各个方面;这就是我问清楚的原因。 (滴,滴)如果您阅读整个教程(用初学者的眼光,谢谢),您会发现“需要”可能是关键但未明确阐述的区别。
  • 第二个引用的第一句片段可以说是模棱两可的(“他们的最顶层节点”是指每个参数的单个最顶层节点,还是每个参数顶部的一些不确定数量的节点?在事实上,后者。)但是“如果模式是一个简单的变量,那么参数就不会被评估”对我来说似乎很明确......
  • 也许我们只是不明白您的问题是什么?确实seq(&&) 的文本示例都将它们的第一个参数简化为WHNF。然而,这并非对所有 Haskell 函数都适用。例如one x = 1 根本不会评估它的论点。同样,notseq x y = y 永远不会评估 x,因为这样做对于生成值 y 不是必需的。

标签: haskell lazy-evaluation


【解决方案1】:

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

所有评估均由模式匹配、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 的早期,seqSeq 类的方法,这很有意义。不幸的是,当“作弊”更容易让seq 为所有事情工作时,实现者发现不得不处理那个类很烦人。所以他们作弊,从那以后程序分析和转换的某些方面变得更加困难。

【讨论】:

  • 所以正确地说(也许有过度简化的风险)不同之处在于,一般来说,评估只发生在找到模式匹配所需的范围内,而seq首先确定是否它的第一个参数不是,这将(通常,离开?)需要进一步评估?
  • @raxacoricofallapatorius,至少含糊其辞。它强制它不进行模式匹配的事物,并且在许多情况下无法进行模式匹配。
  • 你能在primitive numerical operations 上扩展吗?我不认为let a = 2 * 3 强制评估。
  • @Sibi,如果强制使用x * y,则强制使用xy
【解决方案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+2+3+...+1000000 的东西而不是 1+1,这是一种相对便宜的计算,但它的未计算、惰性形式非常长并且占用大量内存,如果表达式是'评估不够快,将有效地开始泄漏内存;这种情况在 Haskell 术语中称为 空间泄漏


编辑:

为了解决您的评论,这是一个虚假场景,其中嵌套数据结构与不同深度的模式匹配。数据结构在每个级别都穿插了trace 调用,以便您可以监控它是如何被评估的:

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 调用的输出,但它实际上很好地展示了评估是如何在运行时发生的。

此外,如果您使用newtype 而不是data 定义NameAge,则评估将略有不同,因为newtype 值没有运行时包装器所以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)

【讨论】:

  • 这是有道理的,但它如何映射到defeuer's answer 中的第二个case 语句——x 的评估在哪里强制执行?
  • 这是因为模式匹配会强制评估未评估块链中的第一个“层”。与seq btw 相同。
  • 那么两个case 语句都会评估它们的第一个参数?这两个示例在第一个参数方面没有表现出不同的行为吗?
  • 我添加了一个更详细的示例,说明模式匹配如何强制评估嵌套数据结构的不同部分。
  • case x 是否强制评估?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-10
  • 2012-11-08
  • 2020-01-28
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多