Scala By Example: 泛型

    技术2022-05-19  21

    泛型二叉搜索树

    通过参数化型别,我们可以将先前的 IntSet 以泛型风格重写

    1: // from IntSet to Set 2: abstract class Set[A] { 3: def incl(x: A): Set[A] 4: def contains(x: A): Boolean 5: } 6: // ... But

    非常直接了当的做法。类似的代码在 Groovy 中将能够顺利编译并(多数情况下)正确运行,但是在 Scala 中,我们将遇到小小的问题:在 IntSet 中,我们在 incl 和 contains 方法中均调用了 Int 的 > 和 < 方法,但当使用泛型的时候,我们无法保证 A 具有这两个方法。为此,我们必须对 A 进行小小的修饰:要求 A 必须已混入了 trait Ordered。

    1: // The parameter declaration A <: Ordered[A] introduces A as a type 2: // parameter which must be a subtype of Ordered[A], i.e. its values must be 3: // comparable to values of the same type. 4: trait Set[A <: Ordered[A]] { 5: def incl(x: A): Set[A] 6: def contains(x: A): Boolean 7: } 8:  9: class EmptySet[A <: Ordered[A]] extends Set[A] { 10: def contains(x: A) = false 11: def incl(x: A): Set[A] = 12: new NonEmptySet(x, new EmptySet[A], new EmptySet[A]) 13: } 14:  15: class NonEmptySet[A <: Ordered[A]](elem: A, left: Set[A], right: Set[A]) 16: extends Set[A] { 17: def contains(x: A): Boolean = 18: if(x < elem) left contains x 19: else if(x > elem) right contains x 20: else true 21: def incl(x: A): Set[A] = 22: if(x < elem) new NonEmptySet(elem, left incl x, right) 23: else if(x > elem) new NonEmptySet(elem, left, right incl x) 24: else this 25: } 26:  27: case class Num(value: Double) extends Ordered[Num] { 28: def compare(that: Num): Int = 29: if(this.value < that.value) -1 30: else if(this.value > that.value) 1 31: else 0 32: } 33:  34: val s = new EmptySet[Num].incl(Num(1.0)).incl(Num(2.0)) 35: println(s.contains(Num(1.5))) 36: // Int does not conform to type parameter bound Ordered[Int] 37: // val w = new EmptySet[Int] // Error! 问题是,如果你在设计 Num 类的时候没有考虑到 Set 的应用,那么你将不会将 Num 声明为 Ordered 的子类,从而无法在日后的场合应用以上的代码,同样的道理,你无法对 EmptySet 应用 Int、Double 或其它显而易见可能会被用到的类。原书中提供了另一中解决方案(view bounds),可惜我无法找到运行它的方法,鉴于 view bounds 将在第 15 节详细介绍,这里就先略过了。#TBD

    协变 & 逆变

    在铅笔书里有那么 1% 的篇幅提到了协变和逆变,并且总结得非常好:本节会让你觉得有点头痛。 默认的情况下,Scala 两者都不支持,通过修饰符 +/- 可以分别实现协/逆变。如同原书所说,其逻辑相当的 conservative。考虑如下代码 1: class Stack[+A] { 2: def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this) 3: }

    通过指定 A 是 B 的子类强迫 push 接收 A 或其基类型。不仅如此,其返回值也会根据 B 的类型自动做相应的改变!(说实话,除了这个自动改变返回值类型的特点外,我一点也无法领会 Scala 在类型处理上的美妙之处,作为强测试的拥护者,我真的真的不在意强类型,but,目前我还没有学完,不是吗?)

    Nothing

    作为递归的起点,空的 Stack 可以写作 class EmptyStack extends Stack[A],但是引入协变后,从逻辑上,我们只需要一个对象就可以表示空栈 1: abstract class Stack[+A] { 2: def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this) 3: def isEmpty: Boolean 4: def top: A 5: def pop: Stack[A] 6: } 7:  8: object EmptyStack extends Stack[Nothing] { 9: def isEmpty = true 10: def top = error("EmptyStack.top") 11: def pop = error("EmptyStack.pop") 12: } 13:  14: class NonEmptyStack[+A](elem: A, rest: Stack[A]) extends Stack[A] { 15: def isEmpty = false 16: def top = elem 17: def pop = rest 18: } 一个对象就可以了。在此,Nothing 是所有类的子类;对应的 Any则是所有类的超类。(突然想吼指环王的台词:One ring to rule them all...)

    Tuple

    刚开始接触元组(没有雪月饼)的时候,我以为这是 Scala 的核心语法,但实际上,所有的功能都由 Tuple 类来提供。它们是如此的方便以至于 Scala 最终为其提供了特殊的语法 (...)。最后提醒下,Tuple 属于 Case 类。

    函数 Technorati 标签: Scala

    不单单元组,函数也是由类来实现的。例如一个二元函数可以视作 trait Function1[-A, +B] { def apply(x: A): B } 的一个实例(看到这些都被解构开来感觉很奇怪)。在这里可以同时看到逆变和协变的应用。 PS. Scala 处理类型转换的方式让人耳目一新,也让人头痛无比。简单的说,我真的不喜欢强类型。 PS II. 但不喜欢归不喜欢,我决定试一试。记得在刚刚开始用 Groovy 时,闭包也让我如堕五里云雾,但用了一年以后,闭包就变成本能了……But,闭包倒是一开始就喜欢。

    最新回复(0)