1. 背景
1.1 子类型
在类型理论中,子类型关系通过S <: T
来表示,它表示S
是T
的一个子类型。
子类型关系具有以下两个性质,
(1)自反性
S <: S
(2)传递性
S <: U U <: T
----------------
S <: T
对于记录类型来说,
假设有类型S = { a:A, b:B, c:C, ...}
,T = { x:X, y:Y, z:Z, ... }
,
S <: T
,当且仅当,S
包含的字段数多于T
。
这看上去有些不可思议,包含字段更多的类型S
,反而“更小”,
这种性质被称为,广度子类型化(width subtyping rule)。
1.2 和类型
和类型是一种带标签的联合类型(tagged union),
通过给各分量增加标签,将它们放在了一起。
类型A
与类型B
的和类型,通常记为A + B
,例如,
Addr = PhysicalAddr + VirtualAddr
通常通过类型标记,来区分Addr的不同分量,
例如,如果pa
是一个PhysicalAddr
,则inl pa
是一个Addr
,
其中,inl
和inr
可以看做是一个函数
inl : PysicalAddr -> PhysicalAddr + VirtualAddr
inr : VirtualAddr -> PhysicalAddr + VirtualAddr
一般的,一个类型A + B
的元素,
是由标记为inl
的A
元素,和标记为inr
的B
元素组成的。
和类型相关的类型推导规则如下,
a: A
------------
inl a: A + B
b: A
------------
inr b: A + B
2. TypeScript
TypeScript中的interface和union types,
分别对应于上文提到的记录类型,与和类型。
(1)记录类型
根据记录类型的广度子类型化规则,以下子类型关系成立,
{a:number, b:number} <: {a:number}
{a:number, b:number} <: {b:number}
即,包含更多字段的{a:number, b:number}
,具有“更小”的类型。
(2)和类型
又根据,和类型的推导规则,
x: {a:number, b:number}
--------------------------
x: {a:number} | {b:number}
即,如果x是类型为{a:number, b:number}
的值,则它也是类型为{a:number} | {b:number}
的值。
3. 问题
但实际上,以上的写法是有问题的,
type A = { a: number };
type B = { b: number };
type AB = A | B;
// Type '{ a: number; b: number; }' is not assignable to type 'A'.
// Object literal may only specify known properties, and 'b' does not exist in type 'A'.
const x: A = { a: 1, b: 1 };
function f(p: A) { }
// Argument of type '{ a: number; b: number; }' is not assignable to parameter of type 'A'.
// Object literal may only specify known properties, and 'b' does not exist in type 'A'.
f({ a: 1, b: 1 });
TypeScript这样的做法,容易使人造成困扰。
因为,这样看起来,{a: number, b: number}
不是{a: number}
的子类型,
也不是{b: number}
的子类型。
然而,如果不是子类型的话,为什么{a: number, b: number}
类型的值,
可以安全的赋值给{a: number} | {b: number}
呢?
const y: AB = { a: 1, b: 1 }; // OK !
TypeScript会出现这样的问题,其实源于它除了进行类型检查之外,
针对对象字面量还多做了一些检查,官网记为,Excess Property Checks。
Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error.
注:
(1)不采用对象字面量(Object literals)的写法是完全没有问题的
type A = { a: number };
type B = { b: number };
type AB = A | B;
const t = { a: 1, b: 1 };
const x: A = t; // OK !
function f(p: A) { }
f(t); // OK !
(2)在对象字面量后面使用as
指定类型,就不会报错了
type A = { a: number };
type B = { b: number };
type AB = A | B;
const x: A = { a: 1, b: 1 } as A; // OK !
function f(p: A) { }
f({ a: 1, b: 1 } as A); // OK !
(3)字面量属性检查,会检查是否传入了过多的属性字段,我们看3个字段的情形
type A = { a: number };
type B = { b: number };
type AB = A | B;
// Type '{ a: number; b: number; c: number; }' is not assignable to type 'AB'.
// Object literal may only specify known properties, and 'c' does not exist in type 'AB'.
const x: AB = { a: 1, b: 1, c: 1 };
function f(p: A) { }
// Argument of type '{ a: number; b: number; c: number; }' is not assignable to parameter of type 'A'.
// Object literal may only specify known properties, and 'b' does not exist in type 'A'.
f({ a: 1, b: 1, c: 1 });
参考
类型和程序设计语言
Excess Property Checks
Union Types
Interfaces