Haskell中的undefined与Java中的null之间有什么区别?

Tom*_*ett 50 java null haskell types maybe

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

编辑

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

对不起,如果看起来我用"评价"这个词快速而宽松地玩......我试图在这里进行类比,并且难以准确地说出它.我想这表明这个比喻是不精确的.

Don*_*art 73

Haskell中的undefined与Java中的null之间有什么区别?

好吧,让我们稍微回顾一下.

Haskell中的"undefined"是"底部"值(表示为?)的示例.这样的值表示程序中任何未定义,卡住或部分状态.

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

undefined本身并不特别 - 它没有连接 - 你可以undefined使用任何底部产生的表达式来实现Haskell .例如,这是一个有效的实现undefined:

 > undefined = undefined
Run Code Online (Sandbox Code Playgroud)

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

 > undefined | False = undefined
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我对列表中的元素做了很多事情:

 > head xs
 ^CInterrupted.
Run Code Online (Sandbox Code Playgroud)

对无限东西的这种操纵是Haskell如此有趣和富有表现力的原因之一.懒惰的结果是Haskell特别关注bottom价值观.

但是,很明显,底部的概念同样适用于Java或任何(非全部)语言.在Java中,有许多表达式产生"底部"值:

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

你只是没有能力很容易地将一个底部替换为另一个底部,而Java编译器对底部值的推理没有太大作用.但是,这些价值观存在.

综上所述,

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

这就是他们的相似之处.

后记

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

  • 首先,Java null本质上等同于在Haskell中为每个类型添加一个隐式Maybe aa.
  • 解除引用null仅相当于Just案例的模式匹配:f (Just a) = ... a ...

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

因此,通过添加null每种类型,您只是bottom意外地创建值更容易- 类型不再帮助您.你的语言不再帮助你防止那种特殊的错误,这很糟糕.

当然,其他底部值仍然存在:异常(如undefined)或无限循环.为每个函数添加一个新的可能失败模式 - 解除引用null- 只是让编写崩溃的程序变得更容易.

  • 啊,这是一个好点; 它不是"null"本身,它是java中的底部值,它是*null的"解除引用". (18认同)
  • @ben不同之处在于,一旦你有未定义的Haskell,你就无法将它改为非底层的东西.这就是您在代码正常工作时避免未定义的原因.在Java中,null是默认值,x = null是完全正确的.这只是取消引用是不正确的.但是您可以将x重新分配给某些非空值.因此,Haskell中的"UndefinedPointerException"只能在发生其他错误之后发生,而在Java中,NullPointerExceptions是失败的原始来源. (3认同)
  • @orm之所以允许`let x = x in x`是因为您实际上可以使用递归定义的值来做非常有用的事情。一个例子是斐波那契序列:`let fibs = 0:1:zipfib中的(+)纤维(tail fibs)产生(相当有效,我应该指出)无限的斐波那契序列。另一个是帕斯卡的三角形:pasc中的“让pasc = [1]:fmap(\ x-> zipWith(+)(0:x)(x ++ [0]))”。我还使用递归定义的数据来有效生成素数,尽管它涉及几个相互依赖的函数,所以我不能轻易地将其粘贴到此处。 (2认同)

sep*_*p2k 7

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

您可以undefined在没有异常的情况下在haskell中传递的唯一原因是haskell是惰性的,除非需要,否则不会评估参数.

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

在Java中null是一个关键字.如果没有null关键字,您将无法定义它(执行相当于haskell定义,即Object myNull = throw(new Exception())不起作用,因为表达式将在那里进行评估).

  • @pelotom:是的,这就是区别:在Haskell中你应该明确表示可能会返回一个非值,而在Java中则不是.这是文化和句法方便的问题,而不是语言本身.如果你愿意,你可以用"错误"丢弃Haskell代码,没有什么能阻止你(除了,可能是对地球上其他所有Haskell程序员的反对). (4认同)
  • @pelotom:无论你多么努力,JLS都说不然.如果您不能接受Java语言规范作为Java语言的规范,那么我们没有基于理性讨论的常见先验. (3认同)
  • @pelotom:这么说不那么准确。例如,如果到那时还没有评估null,就不能编写`if(foo!= null)`(并且推断`foo`也不会被评估,因为`foo`可能为null!)。 (2认同)
  • @pelotom:是的,`&&`和`||`是非严格的.然而,方法应用是严格的.因此,如果将任何表达式作为方法的参数给出,则在方法的主体之前计算表达式.您不能说"将首先评估表达式,*除非*表达式求值为null" - 无法在不进行求值的情况下知道表达式是否计算为null. (2认同)
  • @pelotom:除了JLS第15.7节没有任何这样的漏洞. (2认同)