Haskell 中“case”表达式的求值

ExO*_*ter 15 haskell runtime-error case lazy-evaluation

我目前正在学习 Haskell 并遇到了这个表达式。

\n
statement\n   case 1 \xc2\xb4div\xc2\xb4 0 of _ -> 42\n
Run Code Online (Sandbox Code Playgroud)\n

我的直觉是,由于除以 0,这将导致运行时错误,但从测试来看,情况并非如此。

\n

我的结论是,这一定是由于 Haskell 内部的惰性求值造成的。因为_任何东西的匹配它都不会检查它与什么比较?

\n

因此,是否有人可以告诉我这个评估是否正确,如果不正确,原因是什么。另请详细说明在不实际查看表达式的情况下 case 行可以匹配的前提。

\n

Car*_*arl 15

你的分析很准确,但是这实际上很繁琐。这并不复杂,但需要仔细考虑非常精确的问题。

在哈斯克尔

case 表达式具有一般形式

case exp0 of {pat1 -> exp1 ; ...}
Run Code Online (Sandbox Code Playgroud)

当 case 表达式被求值时,exp0被求值得足够远以确定它是否匹配pat1。如果是这样,则 case 表达式将使用为其创建的exp1任何附加绑定进行计算。pat1如果不匹配,则会进入下一个模式或防护并重复。(如果没有匹配,则计算结果为异常。)

值得注意的是,您的第一个替代方案可以匹配所有内容,无需任何评估。div 1 0因此不进行任何评估。该模式不绑定任何内容,然后 case 表达式的计算结果为 42。

这与您所做的不同:

case 1 `div` 0 of
    0 -> 42
    _ -> 42
Run Code Online (Sandbox Code Playgroud)

在这种情况下,div必须对表达式求值以查看它是否等于 0。这会导致异常,该异常将成为整个 case 表达式的结果。即使两个分支具有相同的值也是如此。模式的内容对于确定与严格性相关的行为非常重要。

核心

GHC Haskell 编译器是迄今为止最常见的,它有一个称为“核心”的中间表示,它看起来与 Haskell 类似,但类型类和大多数语法糖完全脱糖。Core 的语义也与 Haskell 略有不同,特别是在其 case 结构方面。在核心中,case 总是会导致对顶级构造函数的求值。通过嵌套重写模式以匹配原始 Haskell 代码的模式语义。

本节有点旁白,但如果您研究优化 Haskell 代码,您将遇到核心,并且了解其中的差异很有用。


chi*_*chi 10

我的结论是,这一定是由于 Haskell 内部的惰性求值造成的。因为_任何东西的匹配它都不会检查它与什么比较?

对,就是这样!

在模式匹配期间,仅执行匹配模式中的构造函数(和数字/字符文字)所需的评估。如果模式是变量或通配符_,则不需要计算。

case undefined of _ -> ()  -- OK
case undefined of True -> () -- crash
case undefined of (_,_) -> () -- crash
case (undefined, 4) of (_,x) -> x+1 -- OK
Run Code Online (Sandbox Code Playgroud)

此外,模式匹配是从左到右、从上到下进行的:

case (4, undefined) of
   (3, True) -> 1
   (4, _)    -> 2
Run Code Online (Sandbox Code Playgroud)

评估为2- 我们首先匹配4,但在我们尝试匹配3之前失败。通过这种方式,我们避免了崩溃,第二种模式成功了。undefinedTrue(4, _)

(详尽地说:实际上有一个例外。如果您针对newtype构造函数进行模式匹配,则不会发生任何评估。您可以暂时忽略这种情况,因为您正在学习 - 这并不常见。)

  • @ExOster没有深层次的原因,但匹配必须按某种顺序发生,从左到右是很自然的。而且,当我们失败时尽早停止匹配也很方便。 (3认同)