我正在设计一种语言,我想知道默认情况下使引用类型不可为空并使用“?”是否合理。对于可空值和引用类型。这有什么问题吗?你会怎么做:
class Foo {
Bar? b;
Bar b2;
Foo() {
b.DoSomething(); //valid, but will cause exception
b2.DoSomething(); //?
}
}
我正在设计一种语言,我想知道默认情况下使引用类型不可为空并使用“?”是否合理。对于可空值和引用类型。这有什么问题吗?你会怎么做:
class Foo {
Bar? b;
Bar b2;
Foo() {
b.DoSomething(); //valid, but will cause exception
b2.DoSomething(); //?
}
}
我目前的语言设计理念是,可空性应该是程序员被迫要求的,而不是默认情况下在引用类型上给出的(在这一点上,我同意 Tony Hoare - Google 最近的 QCon 演讲)。
在这个特定的例子中,对于不可空的 b2,它甚至不会通过静态检查:保守分析不能保证 b2 不为 NULL,因此程序在语义上没有意义。
我的精神很简单。引用是对某些资源的间接句柄,我们可以遍历它以获取对该资源的访问。可空引用要么是对资源的间接句柄,要么是资源不可用的通知,而且人们永远无法预先确定正在使用哪种语义。这要么预先进行大量检查(它是空的吗?不?是的!),或者不可避免的 NPE(或等价物)。如今,大多数编程资源都没有受到大量资源限制或绑定到某些有限的底层模型 - 简单地说,空引用是......
当然,通过更好的语言选择来解决 NULL 当前满足的每种情况并非易事,并且可能会增加更多的混乱,这会有所帮助。我们总是可以使用不可变的资源,所以 NULL 只是有用的状态(错误和漏洞)并没有太大的实际用途。尽管如此,势在必行的技术仍然存在,坦率地说,我很高兴——这使得在这个领域寻找更好的解决方案变得有价值。
默认情况下引用类型不可为空是唯一合理的选择。我们被搞砸了的语言和运行时所困扰;你应该做正确的事。
此功能在Spec#中。他们默认为可空引用并使用!表示不可为空。这是因为他们想要向后兼容。
在我梦想的语言中(我可能是唯一的用户!)我会做出和你一样的选择,默认情况下不可为空。
我也会将使用 . 可空引用(或任何其他可以取消引用它的)上的运算符。你会如何使用它们?您必须先将它们转换为不可为空的值。你会怎么做?通过测试它们是否为空。
在 Java 和 C# 中,if
语句只能接受bool
测试表达式。我会扩展它以接受可以为空的引用变量的名称:
if (myObj)
{
// in this scope, myObj is non-nullable, so can be used
}
这种特殊的语法对于 C/C++ 程序员来说并不奇怪。我更喜欢这样的特殊语法,以清楚地表明我们正在检查修改myObj
真值分支中名称的类型。
我还要加一点糖:
if (SomeMethodReturningANullable() into anotherObj)
{
// anotherObj is non-nullable, so can be used
}
这只是为anotherObj
左边的表达式的结果命名into
,因此它可以在有效的范围内使用。
我会为操作员做同样的事情?:
。
string message = GetMessage() into m ? m : "No message available";
请注意,这string message
是不可为空的,但上述测试的两个可能结果也是如此,因此赋值是值。
对于将值替换为 null 的可能常见情况,可能会有点糖:
string message = GetMessage() or "No message available";
显然or
只会有效地应用于左侧的可空类型,而右侧的不可空类型。
(对于实例字段,我还有一个内置的所有权概念;编译器会IDisposable.Dispose
自动生成方法,并且~Destructor
语法将用于扩充Dispose
,就像在 C++/CLI 中一样。)
Spec# 有另一个与非空值相关的语法扩展,因为在构造过程中确保非空值已被正确初始化的问题:
class SpecSharpExampleClass
{
private string! _nonNullableExampleField;
public SpecSharpExampleClass(string s)
: _nonNullableExampleField(s)
{
}
}
换句话说,您必须以与使用base
or调用其他构造函数相同的方式初始化字段this
- 当然,除非您直接在字段声明旁边初始化它们。
看看 Java 7 的Elvis 运算符提案。它做了类似的事情,因为它在一个运算符中封装了 null 检查和方法分派,如果对象为 null,则具有指定的返回值。因此:
String s = mayBeNull?.toString() ?: "null";
检查 String s 是否为空,如果是则返回字符串“null”,如果不是则返回字符串的值。也许值得深思。
让可空性成为配置设置,在作者源代码中可执行。这样,您将允许默认情况下喜欢可空对象的人在他们的源代码中享受它们,同时允许那些希望他们的所有对象默认为不可空对象的人完全拥有这些。此外,提供关键字或其他工具来显式标记您的对象和类型声明中的哪些可以为空,哪些不能,使用 and 之类的东西nullable
来not-nullable
覆盖全局默认值。
例如
/// "translation unit 1"
#set nullable
{ /// Scope of default override, making all declarations within the scope nullable implicitly
Bar bar; /// Can be null
non-null Foo foo; /// Overriden, cannot be null
nullable FooBar foobar; /// Overriden, can be null, even without the scope definition above
}
/// Same style for opposite
/// ...
/// Top-bottom, until reset by scoped-setting or simply reset to another value
#set nullable;
/// Nullable types implicitly
#clear nullable;
/// Can also use '#set nullable = false' or '#set not-nullable = true'. Ugly, but human mind is a very original, mhm, thing.
许多人争辩说,给每个人他们想要的东西是不可能的,但如果你正在设计一门新语言,那就尝试新事物。托尼·霍尔(Tony Hoare)在 1965 年引入了这个概念,null
因为他无法抗拒(他自己的话),从那时起我们就为此付出代价(同样,他自己的话,这个人对此感到遗憾)。重点是,聪明、有经验的人犯的错误会让我们其他人付出代价,不要在这个页面上接受任何人的建议,就好像这是唯一的真理,包括我的。评估和思考它。
我读过很多关于我们这些可怜的缺乏经验的程序员真的不知道在哪里真正使用null
和在哪里不使用的咆哮,向我们展示了旨在防止自爆的模式和反模式。一直以来,数百万仍然缺乏经验的程序员使用允许null
. 我可能没有经验,但我知道我的哪些对象不能从可空值中受益。
我认为空值很好:它们清楚地表明您做错了。如果您未能在某处初始化引用,您将立即收到通知。
另一种选择是有时将值初始化为默认值。逻辑错误则更难检测,除非您将检测逻辑放入这些默认值中。这与仅获取空指针异常相同。