在Haskell中,“undefined”和Java中的“null”有什么区别?
好的,让我们稍微回顾一下。
在Haskell中,“undefined”是“底部(bottom)”值的一个示例(表示为⊥)。这样的值表示程序中任何未定义、卡住或部分状态。
许多不同形式的底部存在:非终止循环、异常、模式匹配失败——基本上是程序中某种意义上未定义的状态。值
undefined :: a
是将程序置于未定义状态的典型示例。
undefined
本身并不特别,它不是固定的,你可以使用任何产生底部的表达式来实现Haskell的
undefined
。例如,这是
undefined
的有效实现:
> undefined = undefined
或者立即退出(旧的Gofer编译器使用此定义):
> undefined | False = undefined
bottom的主要属性是,如果一个表达式评估为bottom,那么您的整个程序将评估为bottom:程序处于未定义状态。
为什么需要这样的值?在惰性语言中,您经常可以操作存储bottom值的结构或函数,而程序本身不会成为bottom。
例如,无限循环列表是完全有效的:
> let xs = [ let f = f in f
, let g n = g (n+1) in g 0
]
> :t xs
xs :: [t]
> length xs
2
我对列表的元素无能为力:
> head xs
^CInterrupted.
这种无限的操作是 Haskell 如此有趣和表达性的一部分。懒惰的结果是 Haskell 特别关注底部值。
然而,很明显,底部的概念同样适用于 Java 或任何(非全)语言。在 Java 中,有许多表达式产生“底部”值:
- 将引用与 null 进行比较(请注意,不是 null 本身,因为它是被定义的);
- 除零;
- 数组越界异常;
- 无限循环等。
你不能很容易地替换一个底部为另一个底部,在这方面 Java 编译器并没有对底部值进行推理。但是,这样的值确实存在。
总之:
- 在 Java 中,解引用 null 值是产生底部值的一个具体表达式;
- 在 Haskell 中,undefined 值是一个通用的产生底部值的表达式,可以在 Haskell 中任何需要底部值的地方使用。
这就是它们的相似之处。
补充:
关于 null 本身的问题:为什么它被认为是不好的形式?
首先,Java 的 null 本质上等同于在 Haskell 中的每个类型 a 上添加一个隐式 Maybe a 。
解引用 null 等同于只对 Just 情况模式匹配:f (Just a) = ... a ...
因此,当传入的值为 Nothing(在 Haskell 中)或 null(在 Java 中)时,程序将达到未定义状态。这是不好的:你的程序会崩溃。
因此,通过向每种类型添加 null ,你只需更容易地意外创建底部值 - 类型不再帮助你。你的语言不再帮助你防止特定类型的错误,这是不好的。
当然,其他底部值仍然存在:例如 undefined 异常或无限循环。向每个函数添加一个新的可能失效模式 -- 解引用 null -- 只会使编写崩溃程序变得更容易。
null
更接近的对应词是Haskell语言中的Nothing
。实际上,Java中的每个对象引用都类似于Haskell语言中的Maybe
值。例如,Integer foo = new Integer(5)
就像是let foo = Just 5
,而Integer foo = null
则类似于let foo = Nothing
。 - Chucknull
的行为很像undefined
,除非你可以执行一个操作来检查一个术语是否评估为null
。 - Tom CrockettfromJust
为前缀,如果在Nothing
上使用它会产生一个错误。因此,从理论上讲,它更接近于Nothing
,但在实践中,语义看起来更像是undefined
。 - C. A. McCann==
进行评估的...你唯一允许(安全地)评估 null 的方式是检查它是否为 null。 - Tom Crockett