{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Scala类型系统\n", "Scala类型系统使得编译器能进行很多编译时优化和约束,从而提高运行速度和避免程序错误。通过让编译器来跟踪变量、方法和类的信息,类型系统能帮助我们避免不小心写出的错误代码。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 类型\n", "一个类型就是编译器知道的一组信息。这可以是任何信息。信息可以由用户明确提供的,也可以是由编译器在检查其他代码时自动推断出来的。\n", "\n", "在Scala里可以用以下两种方式定义类型:1. 定义类(class)、特质(trait)或对象(object);2. 直接用type关键字定义类型。\n", "\n", "在Scala里,标注类型的时候可以直接用类或特质的名字来引用其类型。要引用对象的类型,需要用对象的type成员来引用其类型。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mClassName\u001b[0m\n", "defined \u001b[32mtrait \u001b[36mTraitName\u001b[0m\n", "defined \u001b[32mobject \u001b[36mObjectName\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class ClassName\n", "trait TraitName\n", "object ObjectName" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mfoo\u001b[0m\n", "defined \u001b[32mfunction \u001b[36mbar\u001b[0m\n", "defined \u001b[32mfunction \u001b[36mbaz\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def foo(x: ClassName) = x\n", "def bar(x: TraitName) = x\n", "def baz(x: ObjectName.type) = x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 型变(Variance)\n", "型变是指A[T]这样的高阶类型的类型参数可以改变或变化的能力。型变的规则决定了参数化类型的顺应性。型变有三种形式:不变(Invariance)、协变(Covariance)、逆变(Contravariance)。\n", "\n", "Java中参数化的类型在定义时并未声明继承转化关系,而是在使用该类型时,也就是声明变量时,才指定参数化类型的转化行为。\n", "\n", "型变有以下说明:\n", "- 用+T表示某个泛型类的继承关系和参数类型T的集成关系“方向一致”,用-T来表示方向相反。\n", "- 协变适用于表示输出的类型参数,比如不可变集合中的元素。\n", "- 逆变适用于表示输入的类型参数,比如函数参数。" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### 2.1 逆变的例子\n", "逆变不容易理解。逆变的最好例子是一组trait FunctionN,N是介于0到22之间的数字(如scala.Function2),并且与函数所带的参数个数相对应。\n", "\n", "**Scala使用这些trait来实现匿名函数。**比如函数表达式i => i+3是一个语法糖,编译器将其转换为scala.Function1的匿名子类。其实现为:\n", "``` scala\n", "val f: Int => Int = new Function1[Int, Int] {\n", " def apply(i: Int): Int = i + 3\n", "}\n", "```\n", "编译器使用匿名函数的函数体来定义FunctionN抽象特质的apply方法。\n", "\n", "**tips**: 当对象后面跟有参数列表时,就会调用默认的apply函数。比如,一旦定义了f,我们就通过制定参数列表的方式调用它,如f(1)实际上就是f.apply(1)。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "从scala.Function2的声明可以看出:\n", "``` scala\n", "trait Function2[-T1, -T2, +R] extends AnyRef\n", "```\n", "最后一个类型参数+R是返回类型,它是协变的。开头的两个类型参数分别是函数的第一个和第二个参数,它们是逆变的。**FunctionN特质中,函数参数的类型参数都是逆变的。**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**你可以拿一个函数的返回值并转换为其超类。对于参数来说,你可以传入参数类型的子类。**\n", "\n", "你应该能够接受一个Any => String 类型的函数并强制转换为String => Any,但反过来不行。" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mfoo\u001b[0m\n", "defined \u001b[32mfunction \u001b[36mbar\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 方法的隐式型变\n", "def foo(x: Any): String = \"Hello, I received a \" + x\n", "def bar(x: String): Any = foo(x)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres3\u001b[0m: \u001b[32mAny\u001b[0m = Hello, I received a test" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bar(\"test\")" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres4\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"Hello, I received a test\"\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "foo(\"test\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "创建特质来构造函数对象" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mFunction\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "trait Function[-Arg, +Return] {\n", " def apply(arg: Arg): Return\n", "}" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mfoo\u001b[0m: \u001b[32mAnyRef\u001b[0m with \u001b[32mFunction\u001b[0m[\u001b[32mAny\u001b[0m, \u001b[32mString\u001b[0m] = cmd6$$user$$anonfun$1$$anon$1@6734be" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val foo = new Function[Any, String] {\n", " override def apply(arg: Any): String =\n", " \"Hello, I received a \" + arg\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mbar\u001b[0m: \u001b[32mFunction\u001b[0m[\u001b[32mString\u001b[0m, \u001b[32mAny\u001b[0m] = cmd6$$user$$anonfun$1$$anon$1@6734be" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val bar: Function[String, Any] = foo" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres8\u001b[0m: \u001b[32mAny\u001b[0m = Hello, I received a test" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bar(\"test\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.2 为何函数参数必须是逆变,而返回值必须是协变的" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mCSuper\u001b[0m\n", "defined \u001b[32mclass \u001b[36mC\u001b[0m\n", "defined \u001b[32mclass \u001b[36mCSub\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class CSuper {\n", " def msuper() = println(\"CSuper\")\n", "}\n", "\n", "class C extends CSuper {\n", " def m() = println(\"C\")\n", "}\n", "\n", "class CSub extends C {\n", " def msub() = println(\"CSub\")\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "定义一组匿名函数,形式为Function1[C, C],查看函数继承编译规则。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mf\u001b[0m: \u001b[32mC\u001b[0m => \u001b[32mC\u001b[0m = " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var f: C => C = (c: C) => new C" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "f = (c: CSuper) => new CSub" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "f = (c: CSuper) => new C" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "f = (c: C) => new CSub" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:91: type mismatch;", " found : cmd14.this.$ref$cmd9.CSuper", " required: cmd10.INSTANCE.$ref$cmd9.C\r", "f = (c: CSub) => new CSuper\r", " ^\u001b[0m" ] } ], "source": [ "// 因为参数是逆变的,返回值是协变的,故参数无效\n", "f = (c: CSub) => new CSuper" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "当我们约定f的类型是C => C时,其实定义了一个契约。这样,任何有效的C类值都可以传给f,f也永远不会返回除C类值意外的任何值。\n", "\n", "**话说输入类型逆变**:\n", "如果实际函数类型为(x: CSuper) => Csub,该函数不仅可以接受任何C类值作为参数,也可以处理C的父类型实例,或其父类型的其他子类型的实例。所以,由于传入C的实例,我们永远不会传入超出f允许范围外的参数。从某种意义上说,f比我们需要的更加“宽容”。\n", "\n", "**话说输出类型协变**:\n", "当它只返回Csub时,这也是安全的。因为调用方可以处理C的实例,所以也一定可以处理CSub的实例。在这个意义上说,f比我们需要的更加“严格”。\n", "\n", "从调用者的角度来理解,在使用函数的时候,首先看到的是函数的类型声明(上面的例子中,函数类型是C => C)。那么,**函数的输入参数类型应该更加抽象(超类),因为如果函数定义中能够处理具体的,那么传入的对象是超类也能够处理;反之,如果传入的对象是更加具体的,函数体就不知道如何处理具体类的个性成分了。函数的输出类型应该更加具体(子类),不然将超出调用者的预期范围,返回值的内容不应该是所赋变量的超类,而应该是子类。**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.3 方法没有型变标记\n", "型变标记值有在类型声明中的类型参数里才有意义,对参数化方法没有意义。因为该标记影响的是子类继承行为,而方法没有子类。如果试图在方法中加入+或-标记的话,编译器将报错。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.4 可变(var)类型变异\n", "通常情况下,对于某个对象消费的值适合逆变,而对于它产出的值适合协变。\n", "如果一个对象**同时消费和产出某值**,则类型应该保持不变。这通常适用于可变数据结构,比如Scala的数组Array类型就不支持型变。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "对于class中的可变字段,由于存在公有的读写访问方法,将会出现可变类型变异的情况。" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:84: covariant type A occurs in contravariant position in type A of value value_=\r", " class ContainerPlus[+A](var value: A)\r", " ^\u001b[0m" ] } ], "source": [ "// 错误信息指出,我们在使用逆变类型的位置使用了协变类型A\n", "class ContainerPlus[+A](var value: A)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:84: contravariant type A occurs in covariant position in type => A of method value\r", " class ContainerMinus[-A](var value: A)\r", " ^\u001b[0m" ] } ], "source": [ "// 错误信息指出,我们在返回类型处使用了逆变类型A\n", "class ContainerMinus[-A](var value: A)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:84: covariant type A occurs in contravariant position in type A of value a_=\r", " class ContainerPlus[+A](var a: A) {\r", " ^\u001b[0m", "\u001b[31mMain.scala:86: covariant type A occurs in contravariant position in type A of value newA\r", " def value_=(newA: A): Unit = _value = newA\r", " ^\u001b[0m" ] } ], "source": [ "// 这是上面ContainerPlus的显式方法声明重写,与上式等同\n", "class ContainerPlus[+A](var a: A) {\n", " private var _value: A = a\n", " def value_=(newA: A): Unit = _value = newA\n", " def value: A = _value\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "对于getter和setter方法中的可变字段而言,它在读方法中处于协变的位置,而在写方法中又处于逆变的位置。**不存在既协变又逆变的类型参数,所以对于可变字段A的唯一选择就是不变**。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. 型变的例子" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.1 可变集合型变类型为不变\n", "可变数据结构(比如Array、ArrayBuffer、ListBuffer)不支持型变,因为这样做会不安全。\n", "``` scala\n", "val students = new Array[Student](length)\n", "val people: Array[Person] = students //非法,假设可以\n", "people(0) = new Person(\"Jason\") //这样使得students(0)不再是Student了\n", "```" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mAnimal\u001b[0m\n", "defined \u001b[32mclass \u001b[36mDog\u001b[0m\n", "defined \u001b[32mclass \u001b[36mSuperDog\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "trait Animal {\n", " def speak\n", "}\n", "\n", "class Dog(var name: String) extends Animal {\n", " def speak = println(\"woof\")\n", " override def toString = name\n", "}\n", "\n", "class SuperDog(name: String) extends Dog(name) {\n", " def useSuperPower = println(\"Using my superpower!\")\n", "}" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mfido\u001b[0m: \u001b[32mDog\u001b[0m = Fido\n", "\u001b[36mwonderDog\u001b[0m: \u001b[32mSuperDog\u001b[0m = Wonder Dog\n", "\u001b[36mshaggy\u001b[0m: \u001b[32mSuperDog\u001b[0m = Shaggy" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val fido = new Dog(\"Fido\")\n", "val wonderDog = new SuperDog(\"Wonder Dog\")\n", "val shaggy = new SuperDog(\"Shaggy\")" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mcollection.mutable.ArrayBuffer\u001b[0m\n", "\u001b[36mdogs\u001b[0m: \u001b[32mcollection\u001b[0m.\u001b[32mmutable\u001b[0m.\u001b[32mArrayBuffer\u001b[0m[\u001b[32mDog\u001b[0m] = \u001b[33mArrayBuffer\u001b[0m(Fido, Wonder Dog)\n", "\u001b[36mres16_2\u001b[0m: \u001b[32mcollection\u001b[0m.\u001b[32mmutable\u001b[0m.\u001b[32mArrayBuffer\u001b[0m[\u001b[32mDog\u001b[0m] = \u001b[33mArrayBuffer\u001b[0m(Fido, Wonder Dog)\n", "\u001b[36mres16_3\u001b[0m: \u001b[32mcollection\u001b[0m.\u001b[32mmutable\u001b[0m.\u001b[32mArrayBuffer\u001b[0m[\u001b[32mDog\u001b[0m] = \u001b[33mArrayBuffer\u001b[0m(Fido, Wonder Dog)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import collection.mutable.ArrayBuffer\n", "\n", "val dogs = ArrayBuffer[Dog]()\n", "dogs += fido\n", "dogs += wonderDog" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mmakeDogsSpeak\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def makeDogsSpeak(dogs: ArrayBuffer[Dog]) {\n", " dogs.foreach(_.speak)\n", "}" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "woof\n", "woof\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "makeDogsSpeak(dogs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "如果使用makeDogsSpeak(superDogs)将编译无法通过" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:126: type mismatch;", " found : scala.collection.mutable.ArrayBuffer[cmd19.this.$ref$cmd14.SuperDog]", " required: scala.collection.mutable.ArrayBuffer[cmd17.INSTANCE.$ref$cmd14.Dog]", "Note: cmd19.this.$ref$cmd14.SuperDog <: cmd17.INSTANCE.$ref$cmd14.Dog, but class ArrayBuffer is invariant in type A.", "You may wish to investigate a wildcard type such as `_ <: cmd17.INSTANCE.$ref$cmd14.Dog`. (SLS 3.2.10)\r", "makeDogsSpeak(superDogs)\r", " ^\u001b[0m" ] } ], "source": [ "val superDogs = ArrayBuffer[SuperDog]()\n", "superDogs += shaggy\n", "superDogs += wonderDog\n", "makeDogsSpeak(superDogs) // ERROR: won't compile" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.2 不可变集合型变类型为协变\n", "像是List、Vector、Seq这样的不可变集合都支持协变" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mmakeDogsSpeak2\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def makeDogsSpeak2(dogs: Seq[Dog]) {\n", " dogs.foreach(_.speak)\n", "}" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "woof\n", "woof\n" ] }, { "data": { "text/plain": [ "\u001b[36mdogs2\u001b[0m: \u001b[32mSeq\u001b[0m[\u001b[32mDog\u001b[0m] = \u001b[33mList\u001b[0m(Fido, Tanner)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val dogs2 = Seq(new Dog(\"Fido\"), new Dog(\"Tanner\"))\n", "makeDogsSpeak2(dogs2)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "woof\n", "woof\n" ] }, { "data": { "text/plain": [ "\u001b[36msuperDogs2\u001b[0m: \u001b[32mSeq\u001b[0m[\u001b[32mSuperDog\u001b[0m] = \u001b[33mList\u001b[0m(Wonder Dog, Scooby)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// this works too\n", "val superDogs2 = Seq(new SuperDog(\"Wonder Dog\"), new SuperDog(\"Scooby\"))\n", "makeDogsSpeak2(superDogs2)" ] } ], "metadata": { "kernelspec": { "display_name": "Scala 2.11", "language": "scala211", "name": "scala211" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala211", "pygments_lexer": "scala", "version": "2.11.6" } }, "nbformat": 4, "nbformat_minor": 0 }