没有 null 的语言的最佳解释 [英] Best explanation for languages without null

本文介绍了没有 null 的语言的最佳解释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当程序员抱怨 null 错误/异常时,经常有人问我们没有 null 怎么办.

Every so often when programmers are complaining about null errors/exceptions someone asks what we do without null.

我对选项类型的酷炫有一些基本的了解,但我没有知识或语言技能来最好地表达它.对于以下以普通程序员平易近人的方式编写的以下内容的优秀解释是什么,我们可以将其指向该人?

I have some basic idea of the coolness of option types, but I don't have the knowledge or languages skill to best express it. What is a great explanation of the following written in a way approachable to the average programmer that we could point that person towards?

  • 不希望引用/指针默认为可空
  • 选项类型的工作原理,包括简化检查空情况的策略,例如
    • 模式匹配和
    • 单一理解

    推荐答案

    我认为为什么 null 不受欢迎的简洁总结是无意义的状态不应该被表示.

    I think the succinct summary of why null is undesirable is that meaningless states should not be representable.

    假设我正在建模一扇门.它可以处于以下三种状态之一:打开、关闭但未锁定和关闭并锁定.现在我可以按照

    Suppose I'm modeling a door. It can be in one of three states: open, shut but unlocked, and shut and locked. Now I could model it along the lines of

    class Door
        private bool isShut
        private bool isLocked
    

    很清楚如何将我的三个状态映射到这两个布尔变量中.但这留下了第四个不需要的状态:isShut==false &&isLocked==true.因为我选择作为我的表示的类型承认这种状态,所以我必须花费精神上的努力来确保类永远不会进入这种状态(也许通过显式编码一个不变量).相反,如果我使用具有代数数据类型或检查枚举的语言来定义

    and it is clear how to map my three states into these two boolean variables. But this leaves a fourth, undesired state available: isShut==false && isLocked==true. Because the types I have selected as my representation admit this state, I must expend mental effort to ensure that the class never gets into this state (perhaps by explicitly coding an invariant). In contrast, if I were using a language with algebraic data types or checked enumerations that lets me define

    type DoorState =
        | Open | ShutAndUnlocked | ShutAndLocked
    

    然后我可以定义

    class Door
        private DoorState state
    

    再也不用担心了.类型系统将确保 class Door 的实例只有三种可能的状态.这就是类型系统擅长的 - 在编译时明确排除整类错误.

    and there are no more worries. The type system will ensure that there are only three possible states for an instance of class Door to be in. This is what type systems are good at - explicitly ruling out a whole class of errors at compile-time.

    null 的问题在于,每个引用类型都会在其空间中获得这种通常不受欢迎的额外状态.string 变量可以是任何字符序列,也可以是这个疯狂的额外 null 值,它没有映射到我的问题域中.Triangle 对象有三个 Point,它们本身有 XY 值,但不幸的是 Points 或 Triangle 本身可能是这个疯狂的空值,对我正在工作的图形域毫无意义.等等.

    The problem with null is that every reference type gets this extra state in its space that is typically undesired. A string variable could be any sequence of characters, or it could be this crazy extra null value that doesn't map into my problem domain. A Triangle object has three Points, which themselves have X and Y values, but unfortunately the Points or the Triangle itself might be this crazy null value that is meaningless to the graphing domain I'm working in. Etc.

    当您确实打算对可能不存在的值进行建模时,您应该明确选择它.如果我打算模拟人的方式是每个 Person 都有一个 FirstName 和一个 LastName,但只有一些人有 MiddleNames,那么我想说一些类似

    When you do intend to model a possibly-non-existent value, then you should opt into it explicitly. If the way I intend to model people is that every Person has a FirstName and a LastName, but only some people have MiddleNames, then I would like to say something like

    class Person
        private string FirstName
        private Option<string> MiddleName
        private string LastName
    

    这里的 string 被假定为不可为空的类型.然后,在尝试计算某人姓名的长度时,无需建立棘手的不变量,也不会出现意外的 NullReferenceException.类型系统确保任何处理 MiddleName 的代码都考虑到它是 None 的可能性,而任何处理 FirstName 的代码都可以安全地假设那里有一个值.

    where string here is assumed to be a non-nullable type. Then there are no tricky invariants to establish and no unexpected NullReferenceExceptions when trying to compute the length of someone's name. The type system ensures that any code dealing with the MiddleName accounts for the possibility of it being None, whereas any code dealing with the FirstName can safely assume there is a value there.

    例如,使用上面的类型,我们可以编写这个愚蠢的函数:

    So for example, using the type above, we could author this silly function:

    let TotalNumCharsInPersonsName(p:Person) =
        let middleLen = match p.MiddleName with
                        | None -> 0
                        | Some(s) -> s.Length
        p.FirstName.Length + middleLen + p.LastName.Length
    

    没有后顾之忧.相反,在对字符串等类型具有可为空引用的语言中,则假设

    with no worries. In contrast, in a language with nullable references for types like string, then assuming

    class Person
        private string FirstName
        private string MiddleName
        private string LastName
    

    你最终会创作像

    let TotalNumCharsInPersonsName(p:Person) =
        p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
    

    如果传入的 Person 对象不具有所有非空的不变式,则会爆炸,或者

    which blows up if the incoming Person object does not have the invariant of everything being non-null, or

    let TotalNumCharsInPersonsName(p:Person) =
        (if p.FirstName=null then 0 else p.FirstName.Length)
        + (if p.MiddleName=null then 0 else p.MiddleName.Length)
        + (if p.LastName=null then 0 else p.LastName.Length)
    

    也许

    let TotalNumCharsInPersonsName(p:Person) =
        p.FirstName.Length
        + (if p.MiddleName=null then 0 else p.MiddleName.Length)
        + p.LastName.Length
    

    假设 p 确保第一个/最后一个存在,但中间可以为空,或者您可能会进行抛出不同类型异常的检查,或者谁知道是什么.所有这些疯狂的实现选择和要考虑的事情都会突然出现,因为存在您不想要或不需要的愚蠢的可表示值.

    assuming that p ensures first/last are there but middle can be null, or maybe you do checks that throw different types of exceptions, or who knows what. All these crazy implementation choices and things to think about crop up because there's this stupid representable-value that you don't want or need.

    Null 通常会增加不必要的复杂性.复杂性是所有软件的敌人,您应该在合理的情况下努力降低复杂性.

    Null typically adds needless complexity. Complexity is the enemy of all software, and you should strive to reduce complexity whenever reasonable.

    (请注意,即使是这些简单的例子也有更多的复杂性.即使 FirstName 不能为 nullstring 也可以表示""(空字符串),这可能也不是我们打算建模的人名.因此,即使使用不可为空的字符串,我们仍然可能表示无意义的值".同样,您可以选择在运行时通过不变量和条件代码或通过使用类型系统(例如具有 NonEmptyString 类型)来解决这个问题.后者可能是不明智的(好"类型通常在一组常见操作上是封闭的",例如 NonEmptyString 不是在 .SubString(0,0) 上封闭),但它演示了设计空间中的更多点.归根结底,在任何给定的类型系统中,有些复杂性将非常擅长摆脱,而其他复杂性本质上更难以摆脱.关键对于这个主题,几乎在每个类型系统中,从默认情况下可空引用"到默认情况下不可空引用"的变化几乎总是一个简单的变化,它使类型系统变得非常重要更好地应对复杂性并排除某些类型的错误和无意义的状态.因此,如此多的语言一次又一次地重复这个错误,这真是太疯狂了.)

    (Note well that there is more complexity to even these simple examples. Even if a FirstName cannot be null, a string can represent "" (the empty string), which is probably also not a person name that we intend to model. As such, even with non-nullable strings, it still might be the case that we are "representing meaningless values". Again, you could choose to battle this either via invariants and conditional code at runtime, or by using the type system (e.g. to have a NonEmptyString type). The latter is perhaps ill-advised ("good" types are often "closed" over a set of common operations, and e.g. NonEmptyString is not closed over .SubString(0,0)), but it demonstrates more points in the design space. At the end of the day, in any given type system, there is some complexity it will be very good at getting rid of, and other complexity that is just intrinsically harder to get rid of. The key for this topic is that in nearly every type system, the change from "nullable references by default" to "non-nullable references by default" is nearly always a simple change that makes the type system a great deal better at battling complexity and ruling out certain types of errors and meaningless states. So it is pretty crazy that so many languages keep repeating this error again and again.)

    这篇关于没有 null 的语言的最佳解释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆