【问题标题】:Implications of not including NULL in a language?在语言中不包括 NULL 的含义?
【发布时间】:2010-11-29 08:56:09
【问题描述】:

我知道 NULL 在编程语言中不是必需的,我最近决定不在我的编程语言中包含 NULL。声明是通过初始化完成的,所以不可能有未初始化的变量。我希望这将消除 NullPointerException 以支持更有意义的异常或根本没有某些类型的错误。

当然,由于该语言是用 C 实现的,因此在幕后会使用 NULL。

我的问题是,除了使用 NULL 作为错误标志(这是用异常处理)或作为数据结构(如链表和二叉树)的端点(这是用可区分联合处理)之外,还有其他用例吗对于NULL,我应该有一个解决方案?没有 NULL 是否有任何真正重要的影响,这可能会给我带来问题?

【问题讨论】:

标签: programming-languages null language-design


【解决方案1】:

如果有人接受强大的语言应该具有某种指针或引用类型(即可以保存对在编译时不存在的数据的引用)和某种形式的数组类型(或其他方式具有可通过整数索引顺序寻址的存储槽的集合),并且后者的槽应该能够容纳前者,并且人们接受可能必须先读取指针/引用数组的某些槽的可能性所有这些值都存在合理值,那么从编译器的角度来看,将有程序在写入合理值之前读取数组槽(试图在一般情况下确定数组槽之前是否可以读取它被写成相当于停机问题)。

虽然一种语言可能会要求所有数组槽在读取之前使用一些非空引用进行初始化,但在许多情况下,实际上并没有任何可以存储的更好的东西比 null:如果尝试读取尚未写入的数组插槽并取消引用其中包含的(非)项目,则表示错误,并且最好让系统捕获该条件而不是访问一些任意的对象,其存在的唯一目的是为数组槽提供一些它们可以引用的非空值。

【讨论】:

    【解决方案2】:

    我们在应用程序中一直使用空值来表示“无”的情况。例如,如果要求您在给定 id 的数据库中查找某些数据,并且没有记录与该 id 匹配:返回 null。这非常方便,因为我们可以在缓存中存储空值,这意味着如果有人在几秒钟内再次请求该 id,我们不必返回数据库。

    缓存本身有两种不同的响应:null,表示缓存中没有这样的条目,或者是条目对象。条目对象可能有一个空值,当我们缓存一个空数据库查找时就是这种情况。

    我们的应用程序是用 Java 编写的,但即使有未经检查的异常,对异常执行此操作也会非常烦人。

    【讨论】:

    • 数据库空值和语言空值在概念上非常不同(实际上在 C# 中进行了类型区分)。
    • @Imagist:我不是在谈论数据库空值,而是在谈论没有找到记录的情况。对于返回单个对象的方法,您需要某种方式来指示不能返回任何对象。一个异常可以做到这一点,但这样编程非常麻烦,至少在 Java 中是这样。也许你的语言有一些优雅的方法使它更容易。但根据我的经验,null 非常有用。
    【解决方案3】:

    我认为返回 NULL 的方法很有用 - 例如,对于应该返回某个对象的搜索方法,它可以返回找到的对象,如果没有找到,则返回 NULL。

    我开始学习 Ruby,Ruby 有一个非常有趣的 NULL 概念,也许你可以考虑实现一些类似的东西。在 Ruby 中,NULL 被称为 Nil,它是一个实际的对象,就像任何其他对象一样。它恰好被实现为一个全局 Singleton 对象。同样在 Ruby 中,有一个对象 False,在布尔表达式中 Nil 和 False 都计算为 false,而其他所有的计算结果都为 true(例如,即使是 0,也计算为 true)。

    【讨论】:

    • 如果一个方法在其工作中失败了,它应该抛出一个异常。
    • 很多人不喜欢例外。 Joel 有一篇关于该主题的完整博客文章,该文章正在变成本网站上新的 Jamie Zawinski 正则表达式引用。
    • @Chris 链接?此外,值得注意的是,不同的语言使用异常的方式非常不同。在 C++ 中,异常存在内存泄漏危险,而在 Java 中,检查的异常经常被错误地使用。然而,Python 非常有效地使用异常,以至于尝试捕获错误通常比事先检查错误更好。
    • 还有一点需要注意;返回 NULL 比抛出异常并捕获它要慢,尤其是当故障的实际处理发生在调用堆栈上的位置要低得多时。
    • @Imagist:你是​​对的,我的措辞是错误的。我在这里不是指失败。正如我的示例所说,我的意思是当方法应该返回一个对象但找不到该对象时,例如在搜索时。没有NULL,就没有这样的方法。在失败的情况下,应该有例外。我相应地改变了措辞。
    【解决方案4】:

    这里发生了有趣的讨论。

    如果我在构建一种语言,我真的不知道我是否会有null 的概念。我想这取决于我希望语言的外观。恰当的例子:我写了一个简单的模板语言,它的主要优点是嵌套标记和易于使标记成为值列表。它没有 null 的概念,但是它实际上没有任何类型的概念,除了字符串。

    相比之下,它内置的语言 Icon 广泛使用 null。可能 Icon 的语言设计者对 null 所做的最好的事情就是使它与未初始化的变量同义(即,您无法区分一个不存在的变量和一个当前持有 null 值的变量)。然后创建了两个前缀运算符来检查 null 和 not-null。

    在 PHP 中,我有时使用 null 作为“第三个”布尔值。这在“黑盒”类型类(例如 ORM 核心)中很好,其中状态可以是真、假或我不知道。 Null 用于第三个值。

    当然,这两种语言都不像 C 那样有指针,所以不存在空指针。

    【讨论】:

      【解决方案5】:

      我不清楚为什么要从语言中消除“null”的概念。如果您的应用程序需要您“懒惰地”进行一些初始化,您会怎么做——也就是说,您在需要数据之前不执行操作?例如:

      public class ImLazy {
       public ImLazy() {
        //I can't initialize resources in my constructor, because I'm lazy.
        //Maybe I don't have a network connection available yet, or maybe I'm
        //just not motivated enough.
       }
      
       private ResourceObject lazyObject;
       public ResourceObject getLazyObject() { //initialize then return
        if (lazyObject == null) {
         lazyObject = new DatabaseNetworkResourceThatTakesForeverToLoad();
        }
       }
      
       public ResourceObject isObjectLoaded() { //just return the object
        return (lazyObject != null);
       }
      }
      

      在这种情况下,我们如何为 getObject() 返回一个值?我们可以想出以下两种方法之一:

      - 要求用户在声明中初始化 LazyObject。然后用户必须填写一些虚拟对象(UselessResourceObject),这要求他们编写所有相同的错误检查代码(if (lazyObject.equals(UselessResourceObject)...) 或:

      - 想出一些其他值,它的工作方式与 null 相同,但名称不同

      据我所知,对于任何复杂/OO 语言,您都需要此功能或类似功能。拥有非空引用类型可能很有价值(例如,在方法签名中,这样您就不必在方法代码中进行空检查),但空功能应该适用于您使用它。

      【讨论】:

      • 我的语言内置了对 thunk 的支持。在这种情况下,ImLazy() 构造函数将初始化lazyObject = new Thunk(new DatabaseNetworkResourceThatTakesForeverToLoad());(大致;语言的语法不同)。这样,即使调用 getLazyObject(),对象也不会被初始化(当使用它完成某些会导致副作用的事情时它会被初始化)。如果您想在此之前加载对象,可以调用lazyObject.resolve() 来解决thunk。我也在考虑有一个内置线程,它使用空闲周期来尽早解决 thunk。
      • 这似乎是有道理的。如果你有一个带参数的构造函数,我猜你会存储传入的参数,直到 Thunk 初始化对象?
      • 对。您实际上并没有传入对象,而是将构造函数和参数传递给构造函数。
      【解决方案6】:

      Tony Hoare 最近在 LtU 上引用了一篇题为 Null References: The Billion Dollar Mistake 的文章,该文章描述了一种允许在编程语言中存在 NULL,但也消除引用此类 NULL 引用的风险。它看起来很简单,但它却是一个强大的想法。

      更新:这是我阅读的实际论文的链接,其中讨论了 Eiffel 的实现:http://docs.eiffel.com/book/papers/void-safety-how-eiffel-removes-null-pointer-dereferencing

      【讨论】:

      • 我在 λtU 上读过那篇文章,但我最初误解了它的含义。感谢您对此进行纠正。我会详细了解 Eiffel 是如何做到这一点的。
      【解决方案7】:

      Haskell's Maybe monad借用一个页面,您将如何处理可能存在或不存在的返回值的情况?例如,如果您尝试分配内存但没有可用的内存。或者,也许您已经创建了一个数组来容纳 50 个 foo,但还没有一个 foo 被实例化——您需要某种方法来检查这些类型的东西。

      我想您可以使用异常来涵盖所有这些情况,但这是否意味着程序员必须将所有这些情况包装在一个 try-catch 块中?那充其量是烦人的。或者一切都必须返回自己的值加上一个布尔值,指示该值是否有效,这当然不是更好。

      FWIW,我不知道有任何程序没有 some 之类的 NULL 概念——你在所有 C 风格语言中都有 null 和爪哇; Python 有None,Scheme、Lisp、Smalltalk、Lua、Ruby 都有nil; VB使用Nothing;而 Haskell 有一种不同的 nothing

      这并不意味着语言绝对必须有某种空值,但如果所有其他大型语言都使用它,那么它背后肯定有一些合理的推理。

      另一方面,如果您只是制作轻量级 DSL 或其他一些非通用语言,如果您的本机数据类型都不需要它,您可能无需 null 就可以过关。

      【讨论】:

      • 异常是未经检查的,所以如果你能确保某种情况不会发生,你就不必尝试/捕捉。
      • 从技术上讲,您永远无法“确保”某种情况不会发生,除非您完全控制相关硬件和软件的所有方面。
      • @Chris True,但有合理的保证。通常无法确保读取文件、网络连接和用户输入。但是,如果您有 x/2 之类的东西,您可以有理由确定它不会抛出 DivisionByZeroException
      • @lmagist:这有点像稻草人的论点。诸如内存分配之类的事情非常普遍,但永远不能保证成功。
      【解决方案8】:

      我更喜欢将不可为空指针作为默认值的概念,也可以使用可空指针。您几乎可以通过引用 (&) 而不是指针来使用 c++ 执行此操作,但在某些情况下它会变得非常粗糙和令人讨厌。

      Java/C 意义上的语言可以不使用 null,例如 Haskell(和大多数其他函数式语言)有一个“Maybe”类型,它实际上是一种仅提供可选空指针概念的构造。

      【讨论】:

        【解决方案9】:

        在我看来,通常使用 NULL 的用例有两种:

        • 有问题的变量没有值(无)
        • 我们不知道相关变量的值(未知)

        这两种常见情况,老实说,两者都使用 NULL 会引起混淆。

        值得注意的是,一些不支持 NULL 的语言确实支持 Nothing/Unknown 的无。例如,Haskell 支持“Maybe”,它可以包含值或 Nothing。因此,命令可以返回(并接受)它们知道将始终具有值的类型,或者它们可以返回/接受“可能”以指示可能没有值。

        【讨论】:

        • Javascript 区分了 null 和 undefined。
        【解决方案10】:

        立即想到的是传递引用参数。我主要是一名 Objective-C 编码员,所以我习惯于看到这样的事情:

        NSError *error;
        [anObject doSomething:anArgumentObject error:&error];
        // Error-handling code follows...

        此代码执行后,error 对象将包含有关遇到的错误的详细信息(如果有)。但是说我不在乎是否发生错误:

        [anObject doSomething:anArgumentObject error:nil];

        由于我没有为错误对象传递任何实际值,因此我没有得到任何结果,而且我并不真正担心解析错误(因为我一开始并不关心它是否发生) .

        您已经提到您正在以不同的方式处理错误,因此这个特定示例并不真正适用,但重点是:当您通过引用传回某些内容时,您会怎么做?还是您的语言不这样做?

        【讨论】:

        • 我不会通过引用传回东西;一切都是通过常量引用。但是,我很想了解上面的代码是做什么的。我以前从未编写过Objective-C,所以我不知道该代码中发生了什么。你愿意做一个简短的解释吗?
        • 当然。基本上,Objective-C 中不保证异常的错误由 NSError 对象的实例表示。很多方法在执行过程中可能会失败,但不一定;在这种情况下,他们所做的是添加一个引用参数,它是一个 NSError 对象,因此为了找出错误,程序员创建这些对象之一并通过引用方法将其传递。如果该方法遇到问题,它会将详细信息放入对象中,由程序员负责稍后处理。但是,如果程序员不关心错误,他们可以传入 nil。
        猜你喜欢
        • 1970-01-01
        • 2020-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-20
        • 1970-01-01
        相关资源
        最近更新 更多