跳至主要内容

宣布有界多态性

从 Flow 0.5.0 开始,您可以定义具有类型参数界限的多态函数和类。这对于编写需要对类型参数进行一些约束的函数和类非常有用。Flow 的有界多态性语法如下所示

class BagOfBones<T: Bone> { ... }
function eat<T: Food>(meal: T): Indigestion<T> { ... }

问题

考虑以下在 Flow 中定义多态函数的代码

function fooBad<T>(obj: T): T {
console.log(Math.abs(obj.x));
return obj;
}

此代码不会(也不应该!)进行类型检查。并非所有值 obj: T 都具有属性 x,更不用说具有类型为 number 的属性 x 了,这是 Math.abs() 强加的额外要求。

但是,如果您希望 T 不涵盖所有类型,而是仅涵盖具有类型为 numberx 属性的对象的类型呢?直观地说,在给定该条件的情况下,主体应该进行类型检查。不幸的是,在 Flow 0.5.0 之前,您唯一可以强制执行此条件的方法是完全放弃多态性!例如,您可以编写

// Old lame workaround
function fooStillBad(obj: { x: number }): {x: number } {
console.log(Math.abs(obj.x));
return obj;
}

但是,虽然此更改将使主体进行类型检查,但它会导致 Flow 在调用站点之间丢失信息。例如

// The return type of fooStillBad() is {x: number}
// so Flow thinks result has the type {x: number}
var result = fooStillBad({x: 42, y: "oops"});

// This will be an error since result's type
// doesn't have a property "y"
var test: {x: number; y: string} = result;

解决方案

从 0.5.0 版本开始,可以使用有界多态性优雅地解决此类类型问题。诸如 T 之类的类型参数可以指定约束类型参数范围的界限。例如,我们可以编写

function fooGood<T: { x: number }>(obj: T): T {
console.log(Math.abs(obj.x));
return obj;
}

现在,主体在 T{ x: number } 的子类型的假设下进行类型检查。此外,在调用站点之间不会丢失任何信息。使用上面的示例

// With bounded polymorphism, Flow knows the return
// type is {x: number; y: string}
var result = fooGood({x: 42, y: "yay"});

// This works!
var test: {x: number; y: string} = result;

当然,多态类也可以指定界限。例如,以下代码进行类型检查

class Store<T: { x: number }> {
obj: T;
constructor(obj: T) { this.obj = obj; }
foo() { console.log(Math.abs(this.obj.x)); }
}

类的实例化受到适当的约束。如果您编写

var store = new Store({x: 42, y: "hi"});

那么 store.obj 的类型为 {x: number; y: string}

任何类型都可以用作类型参数的界限。该类型不需要是对象类型(如上面的示例所示)。它甚至可以是作用域内的另一个类型参数。例如,考虑将以下方法添加到上面的 Store 类中

class Store<T: { x: number }> {
...
bar<U: T>(obj: U): U {
this.obj = obj;
console.log(Math.abs(obj.x));
return obj;
}
}

由于 UT 的子类型,因此方法主体进行类型检查(正如您所预期的那样,U 也必须满足 T 的界限,这是子类型传递性的结果)。现在,以下代码进行类型检查

  // store is a Store<{x: number; y: string}>
var store = new Store({x: 42, y: "yay"});

var result = store.bar({x: 0, y: "hello", z: "world"});

// This works!
var test: {x: number; y: string; z: string } = result;

此外,在具有多个类型参数的多态定义中,任何类型参数都可能出现在任何后续类型参数的界限中。这对于类型检查以下示例很有用

function copyArray<T, S: T>(from: Array<S>, to: Array<T>) {
from.forEach(elem => to.push(elem));
}

我们为什么要构建它

有界多态性的添加极大地提高了 Flow 类型系统的表达能力,通过使签名和定义能够指定其类型参数之间的关系,而无需牺牲泛型的优势。我们预计表达能力的提高将对库编写者特别有用,并且还将使我们能够为 React 提供的框架 API 编写更好的声明。

转换

与类型注释和其他 Flow 功能一样,多态函数和类定义需要在代码运行之前进行转换。转换在最近发布的 react-tools 0.13.0 中可用