We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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
原文地址
子类型(subtyping)在编程语言理论中一直是个复杂的话题。对协变和逆变的误解是造成这个问题的一个主要原因。这篇文章就是来说明这两个术语的。
接下来我们将会使用以下符号:
假设我们有这么三个类型
Greyhoud <: Dog <: Animal
可以看出Greyhound 是Dog的子类型,并且Dog 是Animal的子类型。通常来说,子类型是具有传递性的,因此Greyhound也是Animal的子类型。
问题: 下面那个类型是Dog -> Dog的子类型
我们如何回答这个问题呢?假设f是一个接收Dog -> Dog 函数类型作为参数的函数。此时我们并不关心f的返回值类型,举个栗子吧,假设:
f: (Dog -> Dog) -> String
现在我想用g作为参数来调用f,接下来我们一一看一下当g是上述类型时会是什么情况:
假设g: Grehound -> Grehound, 那个f(g)是否是类型安全的呢? 不是的,因为f有可能会用其他的Dog的子类型来调用g,比如:GemanShepherd
假设g: Geryhound -> Animal, 那个f(g)是否是类型安全的呢? 不是的,原因同上
假设g: Animal -> Animal, 那个f(g)是否是类型安全的呢? 不是的,因为f有可能调用了g,然后使用它的返回值让他吠,但并不是所有的动物都会吠。
假设g: Animal -> Greyhound, 那个f(g)是否是类型安全的呢? 是的,这个是安全的,f可以使用任意类型的Dog调用g,因为所有的Dog都是Animal,并且,f可以假设g的返回值就是Dog,因为所有的Greyhound都是Dog。
可以看出这个是类型安全的:
(Animal -> Greyhound) <: (Dog -> Dog)
返回值的类型很直接可以看出: Greyhound 是Dog的子类型。但是参数的类型有点儿炸: Animal是Dog的祖(super)类型啊!
为了用我们的行话(jargon)来解释这个奇怪的原因,我们规定参数的返回值类型是协变的,然而参数类型是逆变的。返回值是协变的意味着:A <: B暗指(T -> A) <: (T -> B)(A是B的子类就是说(T -> A)是 (T -> B)的子类型)。参数类型的逆变意味着: A <: B 暗指(B -> T)<: (A -T)(A和B交换位置)
有趣的事实是:在Typescript中,参数类型是双变的(即可以是逆变又可以是协变),很显然这是不合理(unsound)的表现(但是在2.6中可以使用--strictFunctionTypes来修正)
问题: 那么List是List的子类型么?
这问题的答案不是那么好说明的?如果list是不可变的,那么他就是类型安全的,但如果list是可变的,那么他就肯定不是安全的!
为什么呢?假设我需要一个List然后你给我传了一个List,然而我认为你给我传的就是List,那么我就有可能往list中再插入一个Cat, 那么你的List里面就有了一个Cat!显然类型系统是不允许你这么做的。
正式说明: 当我们的list是不可变(数据是否可变)的时候我们是允许类型是可协变的,但是若是可变(数据是否可变)得list那么list类型必须是不可变的(是指类型是否可变,无论是协变还是逆变都是不可以的)。
有趣的事实: 在Java里面,数组即是可变(是指数据是否可变)的又是类型可协变。显然这是不合理的(unsound)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
什么是协变和逆变?
子类型(subtyping)在编程语言理论中一直是个复杂的话题。对协变和逆变的误解是造成这个问题的一个主要原因。这篇文章就是来说明这两个术语的。
接下来我们将会使用以下符号:
一个有意思的问题
假设我们有这么三个类型
可以看出Greyhound 是Dog的子类型,并且Dog 是Animal的子类型。通常来说,子类型是具有传递性的,因此Greyhound也是Animal的子类型。
问题: 下面那个类型是Dog -> Dog的子类型
我们如何回答这个问题呢?假设f是一个接收Dog -> Dog 函数类型作为参数的函数。此时我们并不关心f的返回值类型,举个栗子吧,假设:
现在我想用g作为参数来调用f,接下来我们一一看一下当g是上述类型时会是什么情况:
假设g: Grehound -> Grehound, 那个f(g)是否是类型安全的呢?
不是的,因为f有可能会用其他的Dog的子类型来调用g,比如:GemanShepherd
假设g: Geryhound -> Animal, 那个f(g)是否是类型安全的呢?
不是的,原因同上
假设g: Animal -> Animal, 那个f(g)是否是类型安全的呢?
不是的,因为f有可能调用了g,然后使用它的返回值让他吠,但并不是所有的动物都会吠。
假设g: Animal -> Greyhound, 那个f(g)是否是类型安全的呢?
是的,这个是安全的,f可以使用任意类型的Dog调用g,因为所有的Dog都是Animal,并且,f可以假设g的返回值就是Dog,因为所有的Greyhound都是Dog。
那么接下来该说啥呢?
可以看出这个是类型安全的:
返回值的类型很直接可以看出: Greyhound 是Dog的子类型。但是参数的类型有点儿炸: Animal是Dog的祖(super)类型啊!
为了用我们的行话(jargon)来解释这个奇怪的原因,我们规定参数的返回值类型是协变的,然而参数类型是逆变的。返回值是协变的意味着:A <: B暗指(T -> A) <: (T -> B)(A是B的子类就是说(T -> A)是 (T -> B)的子类型)。参数类型的逆变意味着: A <: B 暗指(B -> T)<: (A -T)(A和B交换位置)
有趣的事实是:在Typescript中,参数类型是双变的(即可以是逆变又可以是协变),很显然这是不合理(unsound)的表现(但是在2.6中可以使用--strictFunctionTypes来修正)
那么其他类型呢?
问题: 那么List是List的子类型么?
这问题的答案不是那么好说明的?如果list是不可变的,那么他就是类型安全的,但如果list是可变的,那么他就肯定不是安全的!
为什么呢?假设我需要一个List然后你给我传了一个List,然而我认为你给我传的就是List,那么我就有可能往list中再插入一个Cat, 那么你的List里面就有了一个Cat!显然类型系统是不允许你这么做的。
正式说明: 当我们的list是不可变(数据是否可变)的时候我们是允许类型是可协变的,但是若是可变(数据是否可变)得list那么list类型必须是不可变的(是指类型是否可变,无论是协变还是逆变都是不可以的)。
有趣的事实: 在Java里面,数组即是可变(是指数据是否可变)的又是类型可协变。显然这是不合理的(unsound)
The text was updated successfully, but these errors were encountered: