我认为对底部进行模式匹配会引起麻烦,因为底部还表示非终止计算,但为什么非终止计算和错误应该被视为同样的情况呢?(我假设使用不支持的参数调用部分函数可以被认为是一种错误。)
如果所有的Haskell类型都包含一个可用于模式匹配的类Java-null的值,而不是底部,将会失去哪些有用的属性?
换句话说,让所有的Haskell函数通过提升所有类型与null值一起实现总函数,这是否明智?
(非终止计算是否需要特殊的类型?)
如果不限制语言的图灵完备性,就无法摆脱非终止问题。由于停机问题,我们一般不能检测到非终止并将其替换为一个值。
因此,每种图灵完备语言都有底部(bottom)。
Haskell和Java之间唯一的区别在于Java具有底部 和 null。Haskell没有后者,这很方便,因为我们就不必检查空值!
换句话说,既然底部在图灵完备世界中是不可避免的,那么除了导致错误之外,也没必要使每个变量都可以为空吧?
还要注意的是,虽然Prelude中的某些函数出于历史原因是偏函数,但现代的Haskell风格倾向于几乎在任何地方编写总函数,并在诸如head
等可能是偏函数的函数中使用显式的Maybe
返回类型。
data Nat = Z | S Nat
的指称只是$\mathbb{Z}$(离散CPO)。也许我漏掉了什么,但在ML中,您不能有一个表示发散的名称,尽管您可以有发散的表达式。 - Philip JF尽管我在评论中有些挑剔,但我认为sclv回答了你问题的第一部分。至于
如果所有的Haskell类型都包含一个可匹配模式的类Java-null值而不是底部(bottom),会失去哪些有用的属性呢?
换句话说: 是否明智地通过提升带有空值的所有类型使得所有Haskell函数都变成总函数?
在这里,你似乎在非终止和异常之间划分了一个区别。虽然由于停机问题,无法对非终止进行模式匹配,但为什么不能对异常进行模式匹配呢?
对此,我也提出了一个问题:那些永远不会引发异常的函数怎么办?毕竟,Haskell有总函数。如果已知函数不会抛出异常,我不应该需要模式匹配来确保其非异常。作为一种束缚和规范语言,Haskell自然会想通过类型来传达这种差异。也许可以通过编写:
Integer
对于已知不是异常情况的整数类型
?Integer
对于可能会出现异常的整数类型,答案是我们已经做到了:Haskell在预定义中有一个类型。
data Maybe a = Just a | Nothing
可以理解为“要么是a
,要么什么都没有”。“Maybe”类型可以进行模式匹配,因此这个提案对我们没有任何帮助。 (我们还有像“Either”这样的类型,用于更丰富的“可能出错的计算”,以及精美的单子语法/组合器,使这些易于处理)。
那么,为什么还需要异常?在Haskell中,我们无法“捕获”异常,除非在IO单子中。如果我们可以使用Maybe
和Either
完美地模拟异常,为什么要在语言中引入异常呢?
这个问题有几个答案,但核心是Haskell的异常不精确。异常可能会发生,因为您的程序耗尽了内存,或者正在执行的线程被另一个线程杀死,或者各种其他不可预测的原因。此外,通常我们关心哪个异常会出现。那么以下表达式应该得到什么结果?
(error "error 1") + (error "error 2") :: Integer
这个表达式显然应该抛出一个异常,但是是哪种异常呢?(+)
在整数上的特化版本会严格检查其两个参数,所以这并没有什么帮助。我们可以决定它是第一个值,但是通常情况下我们会得到
x + y =/= y + x
这将限制我们进行等式推理的选项。Haskell提供了一种具有不精确行为的异常概念,这很重要,因为语言的纯部分具有完全精确的行为,这可能会限制其使用。