Haskell 中的未定义和 Java 中的 null 有什么区别?

2022-08-31 20:14:45

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

编辑

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

抱歉,如果看起来我正在玩“评估”这个词的快速和松散......我试图在这里打个比方,但很难准确地表达出来。我想这是一个迹象,表明这个类比是不精确的。


答案 1

Haskell 中的未定义和 Java 中的 null 有什么区别?

好吧,让我们备份一下。

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

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

undefined它本身并不是特别特别 - 它没有连接 - 你可以使用任何底部屈服的表达式来实现Haskell。例如,这是以下各项的有效实现:undefinedundefined

 > undefined = undefined

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

 > undefined | False = undefined

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.

这种对无限事物的操纵是哈斯克尔如此有趣和富有表现力的部分原因。懒惰的结果是哈斯克尔特别关注价值观。bottom

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

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

你只是没有能力很容易地用一个底部代替另一个底部,Java编译器也没有做很多事情来推理底部值。但是,这些值是存在的。

综上所述,

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

这就是它们的相似之处。

附言

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

  • 首先,Java本质上等同于在Haskell中的每个类型a中添加一个隐式的Maybe anull
  • 仅对于以下情况,取消引用等效于模式匹配:nullJustf (Just a) = ... a ...

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

所以,通过添加到每一种类型中,你只是让偶然创造价值变得更加容易 - 这些类型不再帮助你。你的语言不再能帮助你防止这种特定的错误,这很糟糕。nullbottom

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


答案 2

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

你可以在 haskell 中传递而不会得到异常的唯一原因是 haskell 很懒惰,除非需要,否则不会计算参数。undefined

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

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