2

Scala 中的协变和逆变

 2 years ago
source link: https://blog.yxwang.me/2012/06/variances-in-scala/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java 数组

先来看一个 Java 中的例子,Java 中的数组是协变的。也就是说,一个 String 数组(String[])是可以被当成 Object 数组(Object[])处理的:

String[] a1 = { "abc" };
Object[] a2 = a1;

这种协变虽然在读取数组内容时不会有问题(a1 数组中的 String 元素可以被当成 Object 使用),但是修改数组内容时就会出现无法在编译期检测出来的错误了:

a2[0] = new Integer(17)
String s  = a1[0]  // java.lang.ArrayStoreException

之所以要采用这种设计,Java 的发明者 James Gosling 曾解释说,这样做就能用一种简单通用的方式处理 Java 数组了。例如 java.util.Arrays 提供了sort 方法用于所有数组类型的排序,它的函数声明是 sort(Object[] a, Comparator c),如果 Java 数组不支持协变,那么就很难简单的写出这样通用的排序方法了。

Scala 中的协变和逆变

wikipedia 上关于[协变和逆变的解释](http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)是:

Within the type system of a programming language, covariance and contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations (such as parameters, generics, and return types).

简单来说,它们指定了不同类型相互之间的可转换性。协变类型可以从较普通的类(动物)转换到更精细的类(猫),而逆变则允许从较精细的类(三角形)转换到较普通的类(几何图形)。

Scala 中默认的参数类型是不变(invariant)的,也就是说,下面代码中 Invariant[Object] 类和 Invariant[String] 类的实例,均无法转化成另外一种类型。

scala> class Invariant[T]
defined class Invariant

scala> var x: Invariant[Object] = new Invariant[Object]
x: Invariant[java.lang.Object] = Invariant@8e43b44

scala> var x: Invariant[Object] = new Invariant[String]
<console>:8: error: type mismatch;
 found   : Invariant[String]
 required: Invariant[java.lang.Object]
                                  
scala> var x: Invariant[String] = new Invariant[Object]
<console>:8: error: type mismatch;
 found   : Invariant[java.lang.Object]
 required: Invariant[String]

在 Scala 中,如果要把某一参数类型 T 声明为协变,只需要在它的前面加上 + 号即可:

scala> class Covariant[+T]
defined class Covariant

scala> var x: Covariant[Object] = new Covariant[String]
x: Covariant[java.lang.Object] = Covariant@36527386

类似的,声明 T 为逆变的方式是在它前面加上 - 号。

判断一个类型是逆变、协变还是不变的方法,被称为里氏替换原则(Liskov Substitution Principle)。LSP 指出,如果所有类型 U 出现的地方都能用类型 T 替换,那么 T 就可以被认为是 U 的一个子类型。

协变和逆变有时候可以同时作用在一个类型上,比较经典的一个例子就是 Scala 中的 Function1。当出现类似 A => B 的 lambda 函数时,编译器会自动将它转成一个 Function1[A, B] 的定义。标准库中 Function1 的定义如下:

{% codeblock lang:scala %} trait Function1[-S, +T] { def apply(x: S): T } {% endcodeblock %}

这里 S 是函数参数,T 是函数的返回类型。不难理解,当一个函数 f 能替换另一个函数 g 时,f 接受的参数必须是 g 的父类,而 f 的返回结果必须是 g 的返回结果的子类。因此这里 S 是逆变的,而 T 则是协变的。


Recommend

  • 52
    • hello2dj.github.io 6 years ago
    • Cache

    简话协变和逆变 | hello2dj

    原文地址 什么是协变和逆变?子类型(subtyping)在编程语言理论中一直是个复杂的话题。对协变和逆变的误解是造成这个问题的一个主要原因。这篇文章就是来说明这两个术语的。 接下来我们将会使用以下符号: A &lt;: B 意思是A是B的子类型 A -&gt; B 代表一...

  • 28
    • www.jianshu.com 5 years ago
    • Cache

    Kotlin 范型之协变、逆变 - 简书

    12019.06.24 01:12:48字数 866阅读 1,550 花和草.jpg 一. 类(Class) 与类型(Type) Kotlin 中类和类型是不一样的概念。 下图充分展示了它们的区别。

  • 21

    1. 基本概念 官方:协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。

  • 12

    dotnet 5 从 IL 层面分析协变返回类型新特性在 C# 9.0 里面添加的一个新特性是支持协变返回类型,也就说子类重写了基类的抽象或虚拟方法,可以在返回值里面返回协变的类型,也就是返回值的类型可以是继承原本子类返回值类型的子类。本文将来从 IL 的层面和...

  • 3

    重写 C++函数重写与协变返回类型 1.函数重写(覆盖)满足的条件: 不在同一个作用域(分别在基类和派生类) 函数名相同、参数相同、返回值相同(协变...

  • 2

    在《上篇》中我们揭示了“缺省参数”的本质,现在我们接着来谈谈C#4.0中另一个重要的新特性:协变(Covariance)与逆变(Contravariance)。对于协变与逆变,大家肯定不...

  • 2

    【JAVA冷知识】什么是逆变(contravariant)与协变(covariant)?数组支持协变&逆变吗?泛型呢? 生活不能等待别人来安排,要自己去争取和奋斗;而不论其结果是喜是悲,但可以慰藉的是,你总不枉在这世界上活了一场。有了这样的认识,你就会珍重生活,...

  • 10

    重学c#系列——逆变和协变[二十四] 简单整理一下逆变和...

  • 4

    C#泛型的逆变协变(个人理解) 一般来说, 泛型的作...

  • 4

    五分钟看完,彻底理解协变逆变 其实这是c...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK