
2.1.1 空类型
假设我们在创建一个实用程序库,需要定义这样一个函数:给定一条消息时,记录出现的错误,包括时间戳和消息,然后抛出一个异常,如程序清单2.1所示。这个函数是throw的一个包装器,所以不应该返回值。
程序清单2.1 没有找到配置文件时,引发并记录一个错误

注意,上例中函数的返回类型为never。这清晰地告诉阅读代码的人:raise()从不会返回。更好的是,如果以后有人不小心更新了这个函数,并添加了返回语句,那么代码将不能通过编译。任何值都无法被赋值给never,所以编译器会确保函数的行为一直符合设计,不会返回。
这种类型称为不可赋值类型(uninhabitable type)或空类型,因为我们无法创建它的实例。
空类型:空类型是不能有任何值的类型,其可取值的集合是一个空集合。我们任何时候都无法实例化这种类型的一个变量。我们使用空类型来表示不可能,例如将其用作从不会返回的函数(抛出异常或无限循环的函数)的返回类型。
不可赋值类型用来声明从不返回的函数。函数不返回的原因有几个:函数在所有代码路径上都抛出异常,函数可能执行无限循环,或者可能导致程序崩溃。这些都是合法的场景。我们可能想实现一个函数,让它在发生不可恢复的错误时先做一些记录,发送一些数据,然后再抛出异常或者让程序崩溃。我们可以把想要一直运行的代码放到一个循环中(例如系统的事件处理循环),直到系统关闭。
大多数编程语言使用void来表示不存在有意义的值,但是将上面那样的函数声明为返回void存在误导性。这些函数不是不返回有意义的值,而是根本不返回。
不会终止的函数
空类型看起来无关紧要,但它显示了数学与计算机科学的一个根本区别:在数学中,我们不能定义一个从非空集合到空集合的函数,这根本就没有意义。数学中的函数不用“判断为”这个词来表示结果,它们的结果就是准确的结果。
计算机则判断程序,一步步地执行指令。计算机有可能会判断一个无限循环,这意味着它们将不会结束执行。基于这个原因,计算机程序可以定义一个指向空集合的有意义的函数,前面介绍的函数就是这样的例子。
当你要创建一个不会返回的函数,或者想要明确表示函数不会有返回值的时候,可以考虑使用空类型。
自制空类型
TypeScript提供了never作为空类型,但并不是所有主流语言都提供了内置的空类型。不过在大部分主流语言中,你可以实现一个空类型。为此,你可以定义一个枚举,但不在其中定义任何元素;或者定义一个结构,使其只有一个私有构造函数,这样一来,它将不会被调用。
程序清单2.2显示了在TypeScript中,我们如何把空类型实现为一个无法被实例化的类。注意,如果两个类型具有相似的结构,TypeScript会认为它们是兼容的,所以我们需要添加一个虚拟void属性,以确保其他代码不会产生类型为Empty的值。其他语言,如Java和C#,不需要这个额外的属性,因为它们不会根据形状判断类型是否兼容。第7章将详细介绍这方面的内容。
程序清单2.2 将Empty类型实现为一个不可赋值的类

这段代码能够编译,因为编译器会进行控制流分析,并判断出不需要return语句。另外,因为我们不能创建Empty的实例,所以根本不能添加return语句。