From 8db892a007b38ea7d42a220567f2e0170f01775b Mon Sep 17 00:00:00 2001 From: realwunan Date: Mon, 29 Oct 2018 19:02:52 +0800 Subject: [PATCH 1/2] Simplify Chinese translation of Scala Tour: variances.md --- _zh-cn/tour/variances.md | 142 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/_zh-cn/tour/variances.md b/_zh-cn/tour/variances.md index 49e9e0c066..e819f2e927 100644 --- a/_zh-cn/tour/variances.md +++ b/_zh-cn/tour/variances.md @@ -1,6 +1,6 @@ --- layout: tour -title: Variance +title: 型变 discourse: false @@ -13,3 +13,143 @@ language: zh-cn next-page: upper-type-bounds previous-page: generic-classes --- + +型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持 [泛型类](generic-classes.html) 的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。 + +```tut +class Foo[+A] // A covariant class +class Bar[-A] // A contravariant class +class Baz[A] // An invariant class +``` + +### 协变 + +使用注释 `+A`,可以使一个泛型类的类型参数 `A` 成为协变。 对于某些类 `class List[+A]`,使 `A` 成为协变意味着对于两种类型 `A` 和 `B`,如果 `A` 是 `B` 的子类型,那么 `List[A]` 就是 `List[B]` 的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。 + +考虑以下简单的类结构: + +```tut +abstract class Animal { + def name: String +} +case class Cat(name: String) extends Animal +case class Dog(name: String) extends Animal +``` + +类型 `Cat` 和 `Dog` 都是 `Animal` 的子类型。 Scala 标准库有一个通用的不可变的类 `sealed abstract class List[+A]`,其中类型参数 `A` 是协变的。 这意味着 `List[Cat]` 是 `List[Animal]`,`List[Dog]` 也是 `List[Animal]`。 直观地说,猫的列表和狗的列表都是动物的列表是合理的,你应该能够用它们中的任何一个替换 `List[Animal]`。 + +在下例中,方法 `printAnimalNames` 将接受动物列表作为参数,并且逐行打印出它们的名称。 如果 `List[A]` 不是协变的,最后两个方法调用将不能编译,这将严重限制 `printAnimalNames` 方法的适用性。 + +```tut +object CovarianceTest extends App { + def printAnimalNames(animals: List[Animal]): Unit = { + animals.foreach { animal => + println(animal.name) + } + } + + val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) + val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) + + printAnimalNames(cats) + // Whiskers + // Tom + + printAnimalNames(dogs) + // Fido + // Rex +} +``` + +### 逆变 + +通过使用注释 `-A`,可以使一个泛型类的类型参数 `A` 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 `class Writer[-A]` ,使 `A` 逆变意味着对于两种类型 `A` 和 `B`,如果 `A` 是 `B` 的子类型,那么 `Writer[B]` 是 `Writer[A]` 的子类型。 + +考虑在下例中使用上面定义的类 `Cat`,`Dog` 和 `Animal` : + +```tut +abstract class Printer[-A] { + def print(value: A): Unit +} +``` + +这里 `Printer[A]` 是一个简单的类,用来打印出某种类型的 `A`。 让我们定义一些特定的子类: + +```tut +class AnimalPrinter extends Printer[Animal] { + def print(animal: Animal): Unit = + println("The animal's name is: " + animal.name) +} + +class CatPrinter extends Printer[Cat] { + def print(cat: Cat): Unit = + println("The cat's name is: " + cat.name) +} +``` + +如果 `Printer[Cat]` 知道如何在控制台打印出任意 `Cat`,并且 `Printer[Animal]` 知道如何在控制台打印出任意 `Animal`,那么 `Printer[Animal]` 也应该知道如何打印出 `Cat` 就是合理的。 反向关系不适用,因为 `Printer[Cat]` 并不知道如何在控制台打印出任意 `Animal`。 因此,如果我们愿意,我们应该能够用 `Printer[Animal]` 替换 `Printer[Cat]`,而使 `Printer[A]` 逆变允许我们做到这一点。 + +```tut +object ContravarianceTest extends App { + val myCat: Cat = Cat("Boots") + + def printMyCat(printer: Printer[Cat]): Unit = { + printer.print(myCat) + } + + val catPrinter: Printer[Cat] = new CatPrinter + val animalPrinter: Printer[Animal] = new AnimalPrinter + + printMyCat(catPrinter) + printMyCat(animalPrinter) +} +``` + +这个程序的输出如下: + +``` +The cat's name is: Boots +The animal's name is: Boots +``` + +### 不变 + +默认情况下,Scala中的通用类是不变的。 这意味着它们既不是协变的也不是逆变的。 在下例中,类 `Container` 是不变的。 `Container[Cat]` _不是_ `Container[Animal]`,反之亦然。 + +```tut +class Container[A](value: A) { + private var _value: A = value + def getValue: A = _value + def setValue(value: A): Unit = { + _value = value + } +} +``` + +可能看起来一个 `Container[Cat]` 自然也应该是一个 `Container[Animal]`,但允许一个可变的泛型类成为协变并不安全。 在这个例子中,`Container` 是不变的非常重要。 假设 `Container` 实际上是协变的,下面的情况可能会发生: + +``` +val catContainer: Container[Cat] = new Container(Cat("Felix")) +val animalContainer: Container[Animal] = catContainer +animalContainer.setValue(Dog("Spot")) +val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作为值分配给一只猫 +``` + +幸运的是,编译器在此之前就会阻止我们。 + +### 别的例子 + +另一个可以帮助理型变的例子是来自 Scala 标准库的 `trait Function1[-T, +R]`。 `Function1` 表示具有一个参数的函数,其中第一个类型参数 `T` 表示参数类型,第二个类型参数 `R` 表示返回类型。 `Function1` 在其参数类型上是逆变的,并且在其返回类型上是协变的。 对于这个例子,我们将使用文字符号 `A => B` 来表示 `Function1[A, B]`。 + +假设前面使用过的类似 `Cat`,`Dog`,`Animal` 的继承关系,加上以下内容: + +```tut +abstract class SmallAnimal extends Animal +case class Mouse(name: String) extends SmallAnimal +``` + +假设我们正在处理接受动物类型的函数,并返回他们的食物类型。 如果我们想要一个 `Cat => SmallAnimal`(因为猫吃小动物),但是给它一个 `Animal => Mouse`,我们的程序仍然可以工作。 直观地看,一个 `Animal => Mouse` 的函数仍然会接受一个 `Cat` 作为参数,因为 `Cat` 即是一个 `Animal`,并且这个函数返回一个 `Mouse`,也是一个 `SmallAnimal`。 既然我们可以安全地,无形地用前者代替后者,我们可以说 `Animal => Mouse` 是 `Cat => SmallAnimal` 的子类型。 + +### 与其他语言的比较 + +某些与 Scala 类似的语言以不同的方式支持型变。 例如,Scala 中的型变注释与 C# 中的非常相似,在定义类抽象时添加型变注释(声明点型变)。 但是在Java中,当类抽象被使用时(使用点型变),才会给出型变注释。 From d01e2b29377646c8c36b08d6c653a6d46ada3a3c Mon Sep 17 00:00:00 2001 From: realwunan Date: Tue, 30 Oct 2018 16:54:26 +0800 Subject: [PATCH 2/2] fix 4 inapropriate translation according to dongxuwang's suggestion --- _zh-cn/tour/variances.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_zh-cn/tour/variances.md b/_zh-cn/tour/variances.md index e819f2e927..673792949d 100644 --- a/_zh-cn/tour/variances.md +++ b/_zh-cn/tour/variances.md @@ -114,7 +114,7 @@ The animal's name is: Boots ### 不变 -默认情况下,Scala中的通用类是不变的。 这意味着它们既不是协变的也不是逆变的。 在下例中,类 `Container` 是不变的。 `Container[Cat]` _不是_ `Container[Animal]`,反之亦然。 +默认情况下,Scala中的泛型类是不变的。 这意味着它们既不是协变的也不是逆变的。 在下例中,类 `Container` 是不变的。 `Container[Cat]` _不是_ `Container[Animal]`,反之亦然。 ```tut class Container[A](value: A) { @@ -137,9 +137,9 @@ val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作 幸运的是,编译器在此之前就会阻止我们。 -### 别的例子 +### 其他例子 -另一个可以帮助理型变的例子是来自 Scala 标准库的 `trait Function1[-T, +R]`。 `Function1` 表示具有一个参数的函数,其中第一个类型参数 `T` 表示参数类型,第二个类型参数 `R` 表示返回类型。 `Function1` 在其参数类型上是逆变的,并且在其返回类型上是协变的。 对于这个例子,我们将使用文字符号 `A => B` 来表示 `Function1[A, B]`。 +另一个可以帮助理解型变的例子是 Scala 标准库中的 `trait Function1[-T, +R]`。 `Function1` 表示具有一个参数的函数,其中第一个类型参数 `T` 表示参数类型,第二个类型参数 `R` 表示返回类型。 `Function1` 在其参数类型上是逆变的,并且在其返回类型上是协变的。 对于这个例子,我们将使用文字符号 `A => B` 来表示 `Function1[A, B]`。 假设前面使用过的类似 `Cat`,`Dog`,`Animal` 的继承关系,加上以下内容: @@ -148,7 +148,7 @@ abstract class SmallAnimal extends Animal case class Mouse(name: String) extends SmallAnimal ``` -假设我们正在处理接受动物类型的函数,并返回他们的食物类型。 如果我们想要一个 `Cat => SmallAnimal`(因为猫吃小动物),但是给它一个 `Animal => Mouse`,我们的程序仍然可以工作。 直观地看,一个 `Animal => Mouse` 的函数仍然会接受一个 `Cat` 作为参数,因为 `Cat` 即是一个 `Animal`,并且这个函数返回一个 `Mouse`,也是一个 `SmallAnimal`。 既然我们可以安全地,无形地用前者代替后者,我们可以说 `Animal => Mouse` 是 `Cat => SmallAnimal` 的子类型。 +假设我们正在处理接受动物类型的函数,并返回他们的食物类型。 如果我们想要一个 `Cat => SmallAnimal`(因为猫吃小动物),但是给它一个 `Animal => Mouse`,我们的程序仍然可以工作。 直观地看,一个 `Animal => Mouse` 的函数仍然会接受一个 `Cat` 作为参数,因为 `Cat` 即是一个 `Animal`,并且这个函数返回一个 `Mouse`,也是一个 `SmallAnimal`。 既然我们可以安全地,隐式地用前者代替后者,我们可以说 `Animal => Mouse` 是 `Cat => SmallAnimal` 的子类型。 ### 与其他语言的比较