从 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
不涵盖所有类型,而是仅涵盖具有类型为 number
的 x
属性的对象的类型呢?直观地说,在给定该条件的情况下,主体应该进行类型检查。不幸的是,在 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;
}
}
由于 U
是 T
的子类型,因此方法主体进行类型检查(正如您所预期的那样,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
中可用