{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 类型边界\n", "类型边界是与类型相关的规则,一个变量要匹配一个类型时必须符合这些规则。\n", "\n", "类型边界的两种形式:\n", "- 类型上界(超类型约束,也称为一致性关系)\n", "- 类型下界(子类型约束)\n", "\n", "类型上界是指,某一类型必须是另一种类型的子类型。类型下界表示某类型必须是另一个类型的父类(或该类型本身)。\n", "\n", "**类型边界与型变标记是两个不相干的问题。类型边界对参数化类型所允许采用的类型做了限制,如T <: AnyRef。型变标记表示参数化类型的子类实例是否可以替换父类实例。**" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "实际场景中,常常使用型变标记和类型边界配合的工作方式,这主要是为了解决在错误的位置使用型变参数的问题,下面以Option的getOrElse方法作为例子进行解释:\n", "``` scala\n", "sealed abstract class Option[+A] extends Product with Serializable {\n", " ...\n", " @inline final def getOrElse[B >: A](default: => B): B = {...}\n", " ...\n", "}\n", "```\n", "可以看到,为何getOrElse方法返回B(A的父类型)呢?这里解释原因。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mParent\u001b[0m\n", "defined \u001b[32mclass \u001b[36mChild\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class Parent(val value: Int) { \n", " override def toString = s\"${this.getClass.getName}($value)\"\n", "}\n", "\n", "class Child(value: Int) extends Parent(value)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mop1\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mParent\u001b[0m] = Some(cmd0$$user$Child(1))\n", "\u001b[36mp1\u001b[0m: \u001b[32mParent\u001b[0m = cmd0$$user$Child(1)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val op1: Option[Parent] = Option(new Child(1))\n", "val p1: Parent = op1.getOrElse(new Parent(10))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mop2\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mParent\u001b[0m] = None\n", "\u001b[36mp2a\u001b[0m: \u001b[32mParent\u001b[0m = cmd0$$user$Parent(10)\n", "\u001b[36mp2b\u001b[0m: \u001b[32mParent\u001b[0m = cmd0$$user$Child(100)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val op2: Option[Parent] = Option[Parent](null) // None\n", "val p2a: Parent = op2.getOrElse(new Parent(10)) // Result: Parent(10)\n", "val p2b: Parent = op2.getOrElse(new Child(100)) // Result: Child(100)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mop3\u001b[0m: \u001b[32mOption\u001b[0m[\u001b[32mParent\u001b[0m] = None\n", "\u001b[36mp3a\u001b[0m: \u001b[32mParent\u001b[0m = cmd0$$user$Parent(20)\n", "\u001b[36mp3b\u001b[0m: \u001b[32mParent\u001b[0m = cmd0$$user$Child(200)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val op3: Option[Parent] = Option[Child](null) // None\n", "val p3a: Parent = op3.getOrElse(new Parent(20)) // Result: Parent(20)\n", "val p3b: Parent = op3.getOrElse(new Child(200)) // Result: Child(200)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "关键在这里:\n", "``` scala\n", "val op3: Option[Parent] = Option[Child](null)\n", "val p3a: Parent = op3.getOrElse(new Parent(20))\n", "```\n", "op3显式地将`Option[Child](null)`(即None)赋给了`Option[Parent]`。\n", "\n", "但从调用者的角度,我们并不知道真实类型到底是什么?如果调用者持有对`Option[Parent]`的引用,那么将自然认为它可以从Option[Parent]中提取一个Parent值。故如果是None的话,调用者将返回默认的Parent参数;如果是Some[Parent],则返回Some中的值。**所有情况都认为返回一个Parent类型的值。但实际返回的是Child子类的实例。**如果不适用类型下界说明,那么`val p3a: Parent = op3.getOrElse(new Parent(20))`语句将无法通过类型检查。\n", "\n", "这就是编译器不允许简单的方法签名,而采用[B >: A]边界标记的签名的原因。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**同时使用类型上下界的例子**" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mUpper\u001b[0m\n", "defined \u001b[32mclass \u001b[36mMiddle1\u001b[0m\n", "defined \u001b[32mclass \u001b[36mMiddle2\u001b[0m\n", "defined \u001b[32mclass \u001b[36mLower\u001b[0m\n", "defined \u001b[32mclass \u001b[36mC\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class Upper\n", "class Middle1 extends Upper\n", "class Middle2 extends Middle1\n", "class Lower extends Middle2\n", "case class C[A >: Lower <: Upper](a: A)\n", "// case class C2[A <: Upper >: Lower](a: A) // Does not compile" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 通过引入新的类型参数来解决协变和逆变故障\n", "这里的实例中,我们实现一个List中简化的++版本,将两个集合类型组合起来。\n", "\n", "我们希望能有自动转换功能,比如把字符串列表转换为Any列表,所以把参数类型标注为协变。" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:78: covariant type ItemType occurs in contravariant position in type $user.this.List[ItemType] of value other\r", " def ++(other: List[ItemType]): List[ItemType]\r", " ^\u001b[0m" ] } ], "source": [ "// ++方法定义为接受另一个ItemType类型的里诶包作为参数\n", "// 返回新列表\n", "trait List[+ItemType] {\n", " def ++(other: List[ItemType]): List[ItemType]\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "上面由于ItemType出现在了逆变位置上,出现了编译报错。\n", "\n", "为了绕开编译器限制,我们可以用新类型参数来避免把ItemType放在逆变位置上。" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mList\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 简单绕开型变约束\n", "trait List[+ItemType] {\n", " def ++[OtherItemType](other: List[OtherItemType]): List[ItemType]\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed", "\u001b[31mMain.scala:81: type mismatch;", " found : cmd6.this.$ref$cmd5.List[OtherItemType]", " required: cmd6.this.$ref$cmd5.List[ItemType]\r", " def ++[OtherItemType](other: List[OtherItemType]) = other\r", " ^\u001b[0m" ] } ], "source": [ "// 实现空List类\n", "class EmptyList[ItemType] extends List[ItemType] {\n", " def ++[OtherItemType](other: List[OtherItemType]) = other\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "由于上面定义的方法得到的结果类型不匹配,OtherItemType和ItemType类型不兼容,造成编译失败。\n", "\n", "可以通过对OtherItemType做某种类型约束,使得OtherItemType和ItemType类型建立联系。\n", "\n", "我们希望OtherItemType是能和当前列表很好的组合的类型,因为ItemType是协变的,那么可以把当前列表向ItemType层级上方转换。**因此,我们用ItemType作为OtherItemType的下界约束,我们修正++方法,返回OtherItemType类型。**" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mList\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "trait List[+ItemType] {\n", " def ++[OtherItemType >: ItemType](\n", " other: List[OtherItemType]): List[OtherItemType]\n", "}" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mEmptyList\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class EmptyList[ItemType] extends List[ItemType] {\n", " def ++[OtherItemType >: ItemType](\n", " other: List[OtherItemType]) = other\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "// 确认把各类型的空list组合是否返回我们期望的类型" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mstrings\u001b[0m: \u001b[32mEmptyList\u001b[0m[\u001b[32mString\u001b[0m] = cmd7$$user$EmptyList@fb8491" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val strings = new EmptyList[String]" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mints\u001b[0m: \u001b[32mEmptyList\u001b[0m[\u001b[32mInt\u001b[0m] = cmd7$$user$EmptyList@1cc1a6" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val ints = new EmptyList[Int]" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36manys\u001b[0m: \u001b[32mEmptyList\u001b[0m[\u001b[32mAny\u001b[0m] = cmd7$$user$EmptyList@1b7cf4" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val anys = new EmptyList[Any]" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres11\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mString\u001b[0m] = cmd7$$user$EmptyList@fb8491" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "strings ++ strings" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres12\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mAny\u001b[0m] = cmd7$$user$EmptyList@1cc1a6" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "strings ++ ints" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres13\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mAny\u001b[0m] = cmd7$$user$EmptyList@1b7cf4" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "strings ++ anys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "可以看到,编译器推断出Any是String和Int的共同超类,于是得到了Any列表,这正是我们期望的结果。\n", "\n", "**一般来说,当在类方法里碰到协变和逆变故障时,通常的解决办法是引入一个新的类型参数,在方法签名里用新引入的类型参数。**\n", "\n", "所以,当我们向一个不可变集合添加新元素以构成一个新的集合时(包括上面这个例子),**其类型参数必须具有逆变的行为,但传入的是协变的参数化类型。**\n", "\n", "**总的来说,那些类型参数为协变的参数化类型,与方法参数的类型下界关系密切。**" ] } ], "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 }