-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
typescript polymorphism: Generic #39
Comments
typescript 泛型和类型元编程 https://zhuanlan.zhihu.com/p/82056426 前面讲了typescript关于子类型的一些问题,本文主要讨论Typescript的泛型设计和类型元编程能力。泛型和子类型几乎是正交的两个概念,当然两者也可以配合使用(Bounded Polymorphism)。泛型可以说是Typescript类型系统里最难以理解的部分,因为其涉及非常多type theory的知识,本人对type theory也是一窍不通,只是结合平时的日常使用加以理解。 introduction我们先实现一个简单的函数,用于查找数组的第一个元素
我们发现每次添加一个新的类型,我们都要重新实现一遍该函数,当然我们也可以直接使用any
但这样无法保证返回值的类型和传入参数类型的一致性,这时候使用泛型就比较合理
调用的时候也可以不指明返回类型,可以自动的根据实参推断出类型变量的类型
多个类型变量之间甚至可以建立约束关系
上面的firstElement对于任何的T类型都有效,但是有时候我们的函数实现依赖了类型变量的某些性质,这时候我们需要对类型变量加以约束,来保证我们实现的合法性。
如上述longest函数实现,其要求T类型必须有length属性,这样才可以进行length大小的比较。Typescript中可以通过
这里的
我们查看泛型函数的类型发现,其类型和普通的类型不一致,其类型里包含类型参数
很不幸Typescript缺乏对Generic values的支持,没办法直接声明一个变量类型为泛型(https://github.com/microsoft/TypeScript/issues/17574) 这里的Fn即是type constructor type constructor在typescript里有两个东西功能重合度很大即type alias和interface,这两者实际上都扮演了type constructor的角色(两者有细微的语义差异,这里暂不讨论),后续的type constructor泛指 type alias和interface。type constructor扮演的角色实际上相当于函数的角色,只不过其参数是类型,可以称之为type的函数,其输入是type输出也是type,其甚至有类似if/else的控制结构,实际上type constructor结合extends|infer和对recursive的支持,其本身也近似图灵完全(https://github.com/Microsoft/TypeScript/issues/14833)。 type constructor和Typescript 本身的一些类型运算符实际上构成了type expression,其和 我们通过和普通的js程序进行对比,来展示Typescript 类型是否满足这个三个基本要素。
通过对比我们发现Typescript已经满足了上述的三个基本要素,完全可以进行很灵活的面向类型编程。但是其仍然存在某些限制(如只支持递归,不支持 循环,不支持对number literal进行数学运算等),导致其相比于js编程仍然稍显麻烦。本文通过几个case展示TS类型编程中容易碰到的一些问题 Tuple细心的用户可能会发现,虽然在Javascript中不存在tuple类型(定长异构数组),但是Typescript是有单独的Tuple类型的,其在函数式编程中的类型安全扮演了重要的角色。
Tuple类型的一种重要应用就是定长函数参数的类型实际上tuple类型。
实际上面test函数也可以表达如下,这样可以清楚的看出来,实际上定参的函数参数实际上就是一个单参的tuple(很不幸,javascript不支持tuple。。。)
接下来我们可以对tuple进行一些常规的运算 Head: 获取tuple的第一个元素
Length: 获取tuple的长度借助lookup type可以轻松获取
Tail: 除去第一个的后续元素很自然的想到用infer
很不幸Typescript在数组里目前并不支持这样写 ttps://github.com/microsoft/TypeScript/issues/25719,但是在函数参数里却支持(有点莫名其妙)
last 获取最后一个元素既然我们已经能获取到Tuple的长度了,很自然的想到下述方法
很不幸Typescript目前并不支持对number literal运算的支持microsoft/TypeScript#26382 , 因此我们没办法直接这样操纵,怎么实现呢,读者自己可以想想 conditional type从上面的例子可以看出,类型运算大量的依赖于conditional type,下面研究下conditional type的一些性质
为了方便后续讨论,各参数定义如下:
该表达式虽然简单,但实际上充满了各种edge case。 distributive conditional types在上面的conditional types里,如果我们的 checked type是 naked type那么 conditional types就被称为distributive conditional types。distributive conditional types具有如下性质
如下例所示
嵌套运算并且如果这里的X也是包含T的表达式,即X = G<T>那么此时T在X的表达式也满足U的约束,这实际上促使我们可以进行conditional types的嵌套运算,如下例所示
naked type我们注意到distributive conditional types实际上有三个前提条件
第二点是要求必须要naked type,然而Typescript并没有说明啥是naked type, 我们大致还可以这个type没有被包裹在其他的复合结构里,如 array , record , function等。如我们可以通过将T包裹为[T]来破坏naked type
第三点是要求T实例化为一个union type,这点本来似乎没啥歧义,是不是union一看便知,然而这里的union type还包含了两个看着不像是union的type, any和boolean
出乎意料的是这里的返回结果并不是 why never这里其实还有另一个坑就是never的处理
WTF?res2不就是将res泛型实例化的结果吗?为啥子还不一样呢 A | never = A; 考虑下述运算 type F<T> = T extends U ? X : Y
type F<A> = A extends U ? X : Y // before
// A = A | never
type F<A> = type F<A|never> = A extends U ? X : Y | never extends U ? X : Y // after 我们这这里要保证before和after恒等,那么就必须要保证 我们发现经常出现下述模式
为啥子这里的falseType要用never呢。原因也和distributive conditional type有关。原因还是在于never是union运算的幺元。所以如果我们的conditional types是做某些过滤操作的话,通常合理的做法就是讲falsetype设置为never,这样可以保证一旦某些union的分支判断结果为falseType,就可以过滤掉该分支。如下例所示
type resolve && type check还有一个需要注意的是Typescript类型系统也分为type resolve和type check两部分, type check的结果可能并不影响type infer的结果。考虑下述case
这里虽然约定了T是 extends string的,但是这个约束不像嵌套运算里的讲的约束,嵌套运算里的约束会影响后续 运算结果,而这里的T的约束,只进行type checker,并不影响运算结果。 |
能否说下在开启--strictFunctionTypes 的情况下,怎么解决下面的(ts会报错的)写法: class Base {
handler?: (b: Base) => void;
onSomeEvt(handler: (b: Base) => void) {
this.handler = handler;
}
}
class Child extends Base {
init() {
// 会报错,因为 handler 回调接受的是 Base 类型,虽然 Child 是 Base 的 Derived 类型,
// 但是因为 Child 有一些基础类型没有的属性,所代码可能会报错。
this.onSomeEvt((c: Child) => {
c.work();
});
}
work() {
console.log("work");
}
} 如果用范型的话,会报这样的错误:
update: 一个解决方法是 使用范型引用子类型。然后在父类型需要传递 this 的时候,先将 this 转换成 any
麻烦的是,在子类使用的时候,总要填上一大堆范型参数。
Update(06-07): 上面 VeForm 的例子的 perfect 的写法:towry/n#138 (comment) |
😄 上面的第一个例子找到正确写法了,使用 this 类型可以很好的解决父类型返回子类型的需求。
|
深入typescript类型系统: 泛型
https://zhuanlan.zhihu.com/p/82056426 前面讲了typescript关于子类型的一些类型设计,本文主要讲述关于泛型的一些类型设计。
泛型和子类型几乎是正交的两个概念,当然两者也可以配合使用(Bounded Polymorphism)。泛型可以说是Typescript类型系统里最难以理解的部分,因为其涉及非常多type theory的知识,本人对type theory也是一窍不通,只是结合平时的日常使用加以理解。
introduction
先看一个简单的
type constructor && opaque type alias
inheritance && subtyping
eager && defer type resolve
refinement type && liquid type
conditional type
variadic kinds
higher kinked type & monad
bounded parametric polymorphism
f-bounded polymorphism
recursive type
assignability
algebraic assignability
weaktype && apparent type
widening && fresh literal types
The text was updated successfully, but these errors were encountered: