52

两者都是类型是所有类型(无人居住)的交集的术语。两者都可以在代码中传递而不会失败,直到尝试评估它们。我能看到的唯一区别是,在 Java 中,有一个漏洞允许null对一个操作进行评估,即引用相等比较 ( ==)——而在 Haskellundefined中,根本无法在不抛出异常的情况下进行评估。这是唯一的区别吗?

编辑

对于这个问题,我真正想要解决的是,为什么null在 Java 中包含这样一个明显糟糕的决定,以及 Haskell 是如何逃避它的?在我看来,真正的问题是你可以做一些有用的事情null,即你可以检查它是否为。因为您可以这样做,所以在代码中传递空值并让它们指示“无结果”而不是“此程序中存在逻辑错误”已成为标准约定。而在 Haskell 中,没有办法检查一个术语是否在不评估它的情况下评估到底部并且程序爆炸,所以它永远不能以这种方式使用来表示“没有结果”。相反,人们被迫使用类似Maybe.

抱歉,如果我似乎对“评估”这个词玩得太快了……我试图在这里做一个类比,但很难准确地表达出来。我想这表明这个类比是不精确的。

4

2 回答 2

75

Haskell 中的 undefined 和 Java 中的 null 有什么区别?

好的,让我们稍微备份一下。

Haskell 中的“未定义”是“底部”值的一个示例(表示为 ⊥)。这样的值表示程序中的任何未定义、卡住或部分状态。

存在许多不同形式的底部:非终止循环、异常、模式匹配失败——基本上是程序中在某种意义上未定义的任何状态。该值undefined :: a是将程序置于未定义状态的值的规范示例。

undefined它本身并不是特别特别——它没有undefined内置——你可以使用任何底部收益率表达式来实现 Haskell。例如,这是一个有效的实现undefined

 > undefined = undefined

或立即退出(旧的 Gofer 编译器使用此定义):

 > undefined | False = undefined

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 特别关注bottom价值观。

然而,很明显,底部的概念同样适用于 Java 或任何(非全部)语言。在 Java 中,有许多表达式会产生“底部”值:

  • 将引用与 null 进行比较(注意,不是null它本身,它是明确定义的);
  • 被零除;
  • 越界异常;
  • 无限循环等

您只是没有能力很容易地用一个底部替换另一个底部,而且 Java 编译器并没有做很多关于底部值的推理。然而,这样的价值观是存在的。

总之,

  • 在 Java 中取消引用一个null值是一种在 Java 中产生底部值的特定表达式;
  • Haskell 中的undefined值是一个通用的底部生成表达式,可以在 Haskell 中需要底部值的任何地方使用。

这就是它们的相似之处。

后记

至于null它本身的问题:为什么它被认为是不好的形式?

  • 首先,Javanull本质上相当于在 Haskell 中为每种类型添加一个隐式Maybe aa
  • 取消引用null仅在这种Just情况下等同于模式匹配:f (Just a) = ... a ...

因此,当传入的值是Nothing(在 Haskell 中)或null(在 Java 中)时,您的程序会达到未定义状态。这很糟糕:您的程序崩溃了。

因此,通过添加null每种类型,您可以更轻松地bottom意外创建值 - 类型不再帮助您。您的语言不再帮助您防止这种特定类型的错误,这很糟糕。

当然,其他底部值仍然存在:异常(如undefined)或无限循环。为每个函数添加一个新的可能的故障模式——取消引用null——只会让编写崩溃的程序变得更容易。

于 2010-10-18T21:15:17.243 回答
7

你的描述不太正确。你说null不能评价。然而,由于 java 是一种急切的语言,这意味着f(null)无论定义f是什么都会抛出 NPE(因为方法参数总是在方法运行之前进行评估)。

您可以在 haskell 中传递undefined而不会出现异常的唯一原因是 haskell 是惰性的,除非需要,否则不会评估参数。

undefined 和 null 之间的另一个区别是undefined标准库中定义的简单值。如果它没有在标准库中定义,您可以自己定义它(myUndefined = error "My Undefined例如通过编写)。

在 Javanull中是一个关键字。如果没有null关键字,您将无法定义它(执行与haskell 定义等效的操作,即Object myNull = throw(new Exception()),将不起作用,因为表达式将在那里被评估)。

于 2010-10-18T20:13:20.567 回答