You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
typescript通过 type guard来进行narrowing控制,ts内置了如下的type guard
typeof
functionprintAll(strs: string|string[]|null){if(typeofstrs==="object"){for(constsofstrs){console.log(s)}}elseif(typeofstrs==="string"){console.log(strs)}else{// do nothing}}
truth narrowing
除了0|NaN|""|0n|null|undefined如下几个value为falsy value,其他都是truth value,ts可以通过truth 判定来进行narrowing
A fully-sound type system built on top of existing JS syntax is simply a fool's errand; it cannot be done in a way that produces a usable programming language. Even Flow doesn't do this (people will claim that it's sound; it isn't; they make trade-offs in this area as well).
JS runtime behavior is extremely hostile toward producing a usable sound type system. Getters can return a different value each time they're invoked. The valueOf and toString functions are allowed to do literally anything they want; it's possible that the expression x + y produces a nondeterministic type due to this. The delete operator has side effects which are extremely difficult to describe with a type system. Arrays can be sparse. An object with a own property of type undefined behaves differently from an object with a prototype property of the same value. What could even be done about code like const x = { ["to" + "String"]: 42 } + "oops"; (disallow all computed property names? disallow all primitive values? disallow operator + except when you've... done what, exactly)?
structural typing && nominal typing
大部分面向对象的语言都是nominal typing的,这意味着即使两个类型的结构相同,也互不相容
class Foo {
method(input: string): number { ... }
}
class Bar {
method(input: string): number { ... }
}
let foo: Foo = new Bar(); // ERROR
So far, we’ve used “compatible”, which is not a term defined in the language spec. In TypeScript, there are two kinds of compatibility: subtype and assignment. These differ only in that assignment extends subtype compatibility with rules to allow assignment to and from any, and to and from enum with corresponding numeric values.
事实上typescript大部分都是assignment compatibility检测,即使是在extends的场景如 A extends B ? X : Y 这里检查的是A和B的assignment compatibility。
Different places in the language use one of the two compatibility mechanisms, depending on the situation. For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
/* @flow */
function assign(x:{name: string}, y:{|age: number|}){ // {||}表示exact object type
return Object.assign({},x,y);
}
const x = {name: 'yj'}
const y = {age: 20, name: 'override'}
assign(x, y) //flow 会报错, Cannot call `assign` with `y` bound to `y` because property `name` is missing in object type [1] but exists in object literal [2].
References:
class Noun {x:string}
class City extends Noun {y:string}
class SanFrancisco extends City {z:string}
// 由于ts的class是structual typing,所以
class A{}
class B extends A{} 中A =>B && B=>A ,A和B是等价的,所以需要额外加成员来区分,
在flow里就不用了
可以得知 SanFrancisco => City => Noun
City是Noun的subtype, Noun是City的supertype
ts不支持type variance标注,我们这里使用flow来说明
lettuple=[1,2]as[number,number];lettuple2=[1,2,3]as[number,number,number]lett1: typeoftuple=tuple2;// oklett2: typeoftuple2=tuple;// error because t2[2].toFixed() will cause runtime error
在编程语言和类型论中,polymorphism指为不同的数据类型的实体提供统一的接口。
最常见的polymorphism包括
ad hoc polymorphism (重载)
js因为是动态类型,本身不需要支持重载,直接对参数进行类型判断即可,但是ts为了保证类型安全,支持了函数签名的类型重载,即多个overload signatures和一个implementation signatures
如下 implementation 和 overload 不兼容
因此这里就不要用重载了,直接用union type吧
因此多用union少用overload吧
control flow analysis && narrow
当使用union来实现函数重载时,ts可以通过control flow analysis,将union type narrowing到某个具体的类型,可以帮助我们保证我们的实现更加安全。
narrowing to rescue
side effect
如果control flow含有函数调用,那么可能破坏control flow analysis,若1处虽然ts帮我们推断了arg.x不为null,但是由于alert存在副作用运行时还是可能为null,导致运行时程序挂掉
flow的策略于此相反
flow和ts在这里采用了两种策略,ts更加乐观,默认函数没有副作用,这可能导致runtime error,但是flow更加悲观,默认存在副作用,导致typecheck error这可能导致很多无谓的判断,一个倾向于completeness,一个倾向于sound
副作用标注
支持副作用标注,如果我们可以标注函数的副作用,就可以得到更合理的type check
更加详细的讨论见
microsoft/TypeScript#9998
microsoft/TypeScript#7770
type guard
typescript通过 type guard来进行narrowing控制,ts内置了如下的type guard
除了
0
|NaN
|""
|0n
|null
|undefined
如下几个value为falsy value,其他都是truth value,ts可以通过truth 判定来进行narrowingalgebraic data types && pattern match
上面提到的narrowing只适用于简单的类型如string,boolean,number之类,通常我们可能需要处理更加复杂的类型如不同结构的对象,我们typescript可以通过discriminated union来实现对复杂对象的narrowing操作,discriminated union通常由如下几部分组成
exhaustive check
如我们此时新添加了一个shape类型 type Shape = Circle | Square | Rectangle, typescript会自动告诉我们getArea里需要添加对新加类型的处理
当然我们也可以通过never直接进行检测
基于discriminant union我们实际上实现了ADT(algebraic data type), 在其他的functional programming language里ADT和pattern match配合起来,有着惊人的表达能力,如haskell计算一个树高
上面的算法用ts表述如下,稍显累赘
subtyping polymorphism(子类型)
soundness && completeness
在类型系统中
soundness
指type checker能够reject所有的在运行时可能发生error的代码,这可能导致reject一些运行时不会发生error的代码completeness
指type checker只reject运行时可能发生error的代码,这可能导致有一些运行时可能产生error的代码没被reject理想的情况当然是type checker即是sound也是complete,但这是不可能实现的,只能在两者中做tradeoff,事实上typescript即不sound也不completeness。事实上受限于javascript 的本身缺陷,基于javascript的type checker几乎不可能做到sound,理由如下
structural typing && nominal typing
大部分面向对象的语言都是nominal typing的,这意味着即使两个类型的结构相同,也互不相容
这里的Foo和Bar虽然结构相同由于不是同一个class name,所以并不相容
而对于structual typing,只进行结构比较,并不关心name,所以下述代码在ts里并不会报错
而flow的做法和typescript不同,其对class采用了nominal typing而对普通的对象和function采用了structural typing,
typescript暂时并不支持nominal typing,
但可以通过其他方式进行模拟 https://basarat.gitbooks.io/typescript/docs/tips/nominalTyping.html,
事实上有一个pr正在个typescript添加nominal typing的支持microsoft/TypeScript#33038
subtype && assignment
typescript里的subtype和assignment在大部分情况下等价,我们后续讨论不区分assignment和subtype
事实上typescript大部分都是assignment compatibility检测,即使是在extends的场景如 A extends B ? X : Y 这里检查的是A和B的assignment compatibility。
structural assignability
由于ts暂时不支持nominal subtyping, 我们主要讨论structural subtyping问题.
primitive type
对于primitive type,类型检查很简单
这里的Type可以是null| string | number | Date | Regex 等基本类型,我们只需要直接比较两者的类型是否相等即可,为了简化讨论
我们定义
=>
来表示 isAssignableTo,如下这里的target为subtype, source为 supertype
对于primitive type,=>可以等价于==,但是对于ts还支持其他复杂类型,此时=>和==就不等价了,如
width subtyping
对于structual types,
比较两个对象的是否类型兼容,我们只需要检查两个对象的所有属性的类型是否相同(这里是===并不是 =>,对于对象属性的=>的讨论见depth subtyping)。
如 target: {a:string,b:number} => source: { a: string, b: number}
另一个规则就是 target不能缺少source里的member,如下就会产生错误
这里的target为{x:number} 而source 为{x:number,y:number} ,target里缺少source的y会导致程序出现runtime error。
另一个规则也比较显然
target里在包含了source所有的属性之外还可以包含source之外的属性
上述的代码并不会产生runtime error,所以这条规则似乎也很显然
exact type
但是有时候我们不希望我们的对象有额外的属性,一个常见的场景是object.assign的使用
flow对exact object type进行了支持,保证传入的参数没有额外属性
但是ts尚未支持exact types,有相关pr microsoft/TypeScript#28749
ts采取了另外一种方式来处理exact type的问题
ts对于object literal type进行了EPC(excess property check)
下述代码实际上是会报错的
如果我们不希望进行ECF,则可以通过将object literal 赋值给变量来绕过ECF,因为ECF只适用于object literal
depth subtyping
如果我们有如下两个class
可知Employee是Person的subtype,即Employee => Person,我们可以将Employee赋值给Person的
但是能否将包含Employee属性的对象赋值给包含Person属性的对象呢,考虑下述case
上述代码,在flow里是出错的,在typescript是通过的,显然在这里ts和flow又采取了不同的策略,实际上flow策略更加安全,如如下代码
虽然在ts里正常通过,但是实际上会导致runtime error
这里出错的根源在于person和employee是mutable的,如果他们是immutable的,那么person.who = new Person
这步操作就会被禁止,也就不会导致runtime error,结论就是在mutation情况下,depth subtyping 会导致unsound
事实上typescript不仅仅是object 是covaraint的,数组也是covariant的,同样会导致runtime error
而flow的数组是invariant则更为安全
type variance
简单介绍下各种type variance
我们有三个类
可以得知 SanFrancisco => City => Noun
City是Noun的subtype, Noun是City的supertype
ts不支持type variance标注,我们这里使用flow来说明
invariance 不接受supertype和subtypes
covariance接受subtype不接受supertype
contravariance 接受supertype但不接受subtype
bivarance即接受supertype也接受subtype
事实上flow对于对象属性和数组都是invariant的,而ts对于对象和数组都是coviant的
function subtyping
ts2.6之前对于参数是bivariant的,参数协变其实会产物问题,逆变则没有问题
因此ts在2.6之后,在开启strictFunctionTypes=true的情况下,函数参数变为了contravariant了则更加安全了。
这个规则也可以推理不同参数的函数的subtying情况,
首先考察tuple的subtyping问题
可知 t2 => t1 即tuple长度大的是长度小的子类型
由于ts的多参函数实际可以视为tuple,且function是逆变的,因此可以推得参数少的可以赋值给参数多的
我们再考虑返回值
我们可以看到对于函数返回值应该设计为协变,这里flow和ts都是采用协变,不同的是ts在这里又做了个特殊的处理,考虑如下代码
这里虽然void 既不是string的subtype又不是supertype,但是这里仍然能够通过检查,这里ts故意为之见https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void, 但在flow里是通不过类型检查的
parametric polymorphism (泛型)
type constructor && opaque type alias
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: