当程序员经常抱怨null错误/异常时,有人会问没有null我们该怎么办。
我对选项类型的酷有一些基本的概念,但我没有最好地表达它的知识或语言技能。对于下面的内容,什么样的解释对普通程序员来说是可以理解的呢?
默认情况下,不希望引用/指针为空 选项类型如何工作,包括简化检查空情况的策略,如 模式匹配和 单细胞生物的理解 替代解决方案,如消息吃nil (我错过的其他方面)
当程序员经常抱怨null错误/异常时,有人会问没有null我们该怎么办。
我对选项类型的酷有一些基本的概念,但我没有最好地表达它的知识或语言技能。对于下面的内容,什么样的解释对普通程序员来说是可以理解的呢?
默认情况下,不希望引用/指针为空 选项类型如何工作,包括简化检查空情况的策略,如 模式匹配和 单细胞生物的理解 替代解决方案,如消息吃nil (我错过的其他方面)
当前回答
选项类型的好处并不在于它们是可选的。而是所有其他类型的人都不是。
有时,我们需要能够表示一种“null”状态。有时我们必须表示一个“无值”选项以及变量可能接受的其他可能值。因此,一种完全不允许这一点的语言将会有点残缺。
但通常情况下,我们并不需要它,并且允许这样的“null”状态只会导致歧义和混乱:每次在. net中访问引用类型变量时,我都必须考虑它可能是空的。
通常情况下,它实际上永远不会为空,因为程序员构造的代码使它永远不会发生。但是编译器不能验证它,每次你看到它,你都要问自己“这个可以为空吗?”我需要检查这里是否为空吗?”
理想情况下,在许多情况下,null没有意义,它不应该被允许。
在. net中实现这一点很棘手,因为几乎所有东西都可以为空。您必须依赖所调用代码的作者100%遵守纪律和一致,并清楚地记录了什么可以为空,什么不可以为空,否则您必须偏执狂,检查所有内容。
但是,如果默认情况下类型不能为空,则不需要检查它们是否为空。你知道它们永远不可能为空,因为编译器/类型检查器会强制你这样做。
然后,我们只需要一个后门,在极少数情况下,我们确实需要处理一个空状态。然后可以使用“选项”类型。然后,在我们有意识地决定我们需要能够表示“无值”的情况下,我们允许null,而在其他任何情况下,我们知道值永远不会为null。
正如其他人所提到的,在c#或Java中,null可能意味着以下两种情况之一:
变量未初始化。理想情况下,这种情况不应该发生。一个变量不应该存在,除非它被初始化。 变量包含一些“可选”数据:它需要能够表示没有数据的情况。这有时是必要的。也许你试图在一个列表中找到一个对象,而你事先不知道它是否在那里。然后我们需要能够表示“没有找到对象”。
第二种意思必须保留,但第一种意思应该完全消除。甚至第二种意思也不应该是默认的。如果我们需要,我们可以选择加入。但当我们不需要某些东西为可选时,我们希望类型检查器保证它永远不会为空。
其他回答
默认情况下,不希望引用/指针为空。
我不认为这是null的主要问题,null的主要问题是它们可能意味着两件事:
引用/指针是未初始化的:这里的问题与一般的可变性相同。首先,它使分析代码变得更加困难。 变量为空实际上意味着一些事情:这是Option类型实际形式化的情况。
支持Option类型的语言通常也禁止或不鼓励使用未初始化的变量。
选项类型的工作方式包括简化检查null情况的策略,例如模式匹配。
为了有效,需要在语言中直接支持Option类型。否则就需要大量样板代码来模拟它们。模式匹配和类型推断是使Option类型易于使用的两个关键语言特性。例如:
在f#:
//first we create the option list, and then filter out all None Option types and
//map all Some Option types to their values. See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]
//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")
然而,在像Java这样没有直接支持Option类型的语言中,我们会有这样的东西:
//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
if(op instanceof Some)
filteredList.add(((Some<Integer>)op).getValue());
替代解决方案,如消息吃nil
Objective-C's "message eating nil" is not so much a solution as an attempt to lighten the head-ache of null checking. Basically, instead of throwing a runtime exception when trying to invoke a method on a null object, the expression instead evaluates to null itself. Suspending disbelief, it's as if each instance method begins with if (this == null) return null;. But then there is information loss: you don't know whether the method returned null because it is valid return value, or because the object is actually null. It's a lot like exception swallowing, and doesn't make any progress addressing the issues with null outlined before.
选项类型的好处并不在于它们是可选的。而是所有其他类型的人都不是。
有时,我们需要能够表示一种“null”状态。有时我们必须表示一个“无值”选项以及变量可能接受的其他可能值。因此,一种完全不允许这一点的语言将会有点残缺。
但通常情况下,我们并不需要它,并且允许这样的“null”状态只会导致歧义和混乱:每次在. net中访问引用类型变量时,我都必须考虑它可能是空的。
通常情况下,它实际上永远不会为空,因为程序员构造的代码使它永远不会发生。但是编译器不能验证它,每次你看到它,你都要问自己“这个可以为空吗?”我需要检查这里是否为空吗?”
理想情况下,在许多情况下,null没有意义,它不应该被允许。
在. net中实现这一点很棘手,因为几乎所有东西都可以为空。您必须依赖所调用代码的作者100%遵守纪律和一致,并清楚地记录了什么可以为空,什么不可以为空,否则您必须偏执狂,检查所有内容。
但是,如果默认情况下类型不能为空,则不需要检查它们是否为空。你知道它们永远不可能为空,因为编译器/类型检查器会强制你这样做。
然后,我们只需要一个后门,在极少数情况下,我们确实需要处理一个空状态。然后可以使用“选项”类型。然后,在我们有意识地决定我们需要能够表示“无值”的情况下,我们允许null,而在其他任何情况下,我们知道值永远不会为null。
正如其他人所提到的,在c#或Java中,null可能意味着以下两种情况之一:
变量未初始化。理想情况下,这种情况不应该发生。一个变量不应该存在,除非它被初始化。 变量包含一些“可选”数据:它需要能够表示没有数据的情况。这有时是必要的。也许你试图在一个列表中找到一个对象,而你事先不知道它是否在那里。然后我们需要能够表示“没有找到对象”。
第二种意思必须保留,但第一种意思应该完全消除。甚至第二种意思也不应该是默认的。如果我们需要,我们可以选择加入。但当我们不需要某些东西为可选时,我们希望类型检查器保证它永远不会为空。
Robert Nystrom在这里提供了一篇不错的文章:
http://journal.stuffwithstuff.com/2010/08/23/void-null-maybe-and-nothing/
描述了他在为Magpie编程语言添加缺席和失败支持时的思维过程。
矢量语言有时可以不使用null。
在本例中,空向量充当类型化null。
微软研究院有个有趣的项目叫做
spec#
它是一个c#扩展,具有非空类型和一些机制来检查你的对象是否为空,尽管,恕我直言,应用契约设计原则可能更合适,对许多由空引用引起的麻烦情况更有帮助。