【问题标题】:Why Haskell uses bottom instead of null in partial functions?为什么 Haskell 在偏函数中使用底部而不是 null?
【发布时间】:2013-02-03 20:07:07
【问题描述】:

我正在阅读有关 Haskell 指称语义 (http://en.wikibooks.org/wiki/Haskell/Denotational_semantics) 的文章,但我不明白为什么在一个类型中,与“正常”值相比,底部“值”位于另一个级别,例如为什么不能模式匹配。

我相信模式修补底部会造成麻烦,因为底部也表示非终止计算,但为什么非终止计算和错误应该被同等对待? (我假设使用不受支持的参数调用部分函数可以被视为错误)。

如果所有 Haskell 类型都包含一个模式匹配的 Java-null-like 值而不是底部,那么会丢失哪些有用的属性?

换句话说:为什么不明智地通过提升所有类型的空值来使所有 Haskell 函数合计?

(非终止计算是否需要特殊类型?)

【问题讨论】:

  • @Aivar 事实上,最近的 OO 语言(如 Kotlin 和 Ceylon)开始区分,例如 String 和 String?,后者可以为 null,但不能为前者,应该让你重新思考你的问题。我敢打赌,如果今天发明了 Java 等,null 将被禁止或以类似于 Haskell 的 Maybe 的方式限制。
  • @Ingo:实际上,我完全支持类型系统,以防程序员应该始终检查某个东西是“正常”值还是“错误值”——我满足于使用也许/要么在那里。我更多地考虑真正特殊的结果,以及模式匹配它们的可能性。现在我看到我不仅想要空值,还想要一组异常值。但是在考虑了更多之后,我必须承认我不再确定这些真正非凡的结果是否值得进行模式匹配(例如,OutOfMemory 可能不值得)。

标签: haskell types


【解决方案1】:

在不限制语言的图灵完备性的情况下,您无法摆脱不终止,并且由于停止问题,我们通常无法检测到不终止并将其替换为值。

所以每一种图灵完备的语言都有底。

Haskell 和 Java 之间的唯一区别是 Java 的底部 为空。 Haskell 没有后者,这很方便,因为这样我们就不必检查空值了!

换句话说,既然底部是不可避免的(在图灵完备的世界中),那么除了引起错误之外,使所有东西都可以为空又有什么意义呢?

还要注意,虽然 Prelude 中的一些函数由于历史原因是部分函数,​​但现代 Haskell 风格倾向于在几乎所有地方编写全部函数,并在函数中使用显式 Maybe 返回类型,例如 head,否则会是部分函数。

【讨论】:

  • 我认为这不公平。在严格的语言中,非终止是一种效果,而在非严格的语言中,它是一个值。在说 ML 中你不能有一个“无人居住”类型的值,尽管你可以有一个函数 `a -> Void 因为函数而不是类型被提升了。
  • @PhilipJF:如果您有指称方法,则不终止始终是您的语义域中的一个值。如果您像 Bob Harper 并且不相信指称语义,您可以声称不终止是一种效果,并且它在 ML 中不存在。我认为 Bob Harper 知道的很多,但我也认为 Scott 和 Strachey 在这方面比他更了解。
  • @sclv,在严格的语言中,值的语义域不同于计算的语义域。后者存在底部(鲍勃·哈珀(Bob Harper)不会否认这一点),但前者不存在。类型对应值域,所以被底部所占据。
  • 您有该声明的来源吗? AFAIK 对于不区分值和表达式之类的东西的严格语言,您不能有语义。这就是为什么我见过的任何像 ML 这样的符号都有类似 [a -> b] = [a] -> ([b] + 1)
  • 我还是不明白你在说什么。我不是指称语义方面的专家,但我认为 CBV 语言的标准模型使用 pCpo 并且没有给你底线。所以data Nat = Z | S Nat 的表示就是 $\mathbb{Z}$(离散的 CPO)。也许我遗漏了一些东西,但是在 ML 中你不能有一个表示分歧的名字,尽管你可以有分歧的表达。
【解决方案2】:

我在 cmets 中的狡辩无法忍受,我认为 sclv 回答了你问题的第一部分,但至于

如果所有 Haskell 类型都包含一个模式匹配的 Java-null-like 值而不是底部,那么会丢失哪些有用的属性?

换句话说:为什么不明智地通过提升所有类型的空值来使所有 Haskell 函数总计?

在这里,您似乎在区分非终止和异常。那么,虽然不可能(由于停机问题)在非终止时进行模式匹配,但为什么不能在异常时进行模式匹配呢?

我用我自己的一个问题来回答:那些从不抛出异常的函数呢? Haskell 毕竟具有全部功能。如果已知某些东西是非异常的,我不应该使用模式匹配来确保它是非异常的。 Haskell 作为一种束缚和纪律语言,自然会想在类型上传达这种差异。也许通过写作

Integer

对于已知不是异常的整数类型和

?Integer

对于可能是异常的整数类型。答案是我们已经这样做了:Haskell 在前奏中有一个类型

data Maybe a = Just a | Nothing

可以读作“a 或者什么都没有”。我们可以在Maybe 上进行模式匹配,所以这个提议没有给我们任何东西。 (我们也有像 Either 这样的类型,用于更丰富的“可能出错的计算”,以及花哨的 monad 语法/组合器,使它们易于使用)。

那么,为什么会有例外呢?在 Haskell 中,除了 IO monad 之外,我们不能“捕获”异常。如果我们可以用MaybeEither 完美地模拟异常,为什么语言中会有异常?

对此有几个答案,但核心是 Haskell 异常是不精确的。可能会出现异常,因为您的程序内存不足,或者您正在执行的线程被另一个线程杀死,或者一大堆其他不可预测的原因。此外,通常情况下,我们关心 哪个 异常我们会退出。那么下面的表达式应该是什么结果呢?

(error "error 1") + (error "error 2") :: Integer

这个表达式显然应该导致异常,但是哪个异常呢? (+) 专门用于 Integer 的两个参数都很严格,所以这无济于事。我们可以决定它是第一个值,但通常我们会拥有

x + y =/= y + x

这将限制我们进行等式推理的选择。 Haskell 提供了一种行为不精确的异常概念,这很重要,因为语言的纯部分具有完全精确的行为,并且可能会受到限制。

【讨论】:

  • 谢谢!我同意第一部分,您应该能够静态地区分总函数和部分函数,​​而 Maybe/Either 是一个很好的方法。
  • 现在,不精确的异常和捕获的麻烦——如果 Haskell 在这里使用一些适当的值(例如异常字符串)而不是底部,这件事不会得到解决。像底部一样,这些值将是所有类型的,并且会通过计算传播,但它们将是模式匹配的(即可捕获的)。通常不会为他们写案例,因为他们真的很特别,但如果有人愿意这样做,那是可能的。
  • 当我在这里时,上述维基页面 (en.wikibooks.org/wiki/Haskell/…) 中有一个部分说模式匹配底部会很糟糕,因为这会破坏单调性,单调性是好的,但我看不到为什么好。 (如果我们将这些异常值与适当的值放在同一水平,我们甚至可以保持单调性)。我应该再写一个关于这个的问题吗?
  • 如果error 被命名为更清晰的名称,例如impossible,那就太好了。它永远不应该用于可能发生在正确代码中的代码路径,并且专为您想要使线程/程序崩溃时而设计。捕获它的唯一原因是写入长期运行的服务器中的日志文件。
猜你喜欢
  • 2019-11-23
  • 2015-12-06
  • 1970-01-01
  • 2014-08-25
  • 2016-10-06
  • 2011-02-21
  • 1970-01-01
  • 1970-01-01
  • 2013-05-29
相关资源
最近更新 更多