Jade Dungeon

泛型与类型转换高级应用

泛型与类型转换高级应用

多重界定

多重泛型界定

泛型变量可以同时有上界与下界:

T >: Lower <: Upper

虽然同时有多个上界或下界是不可以的,但可以要求一个类型实现多个特质:

T <: Comparable[T] with Serializable with Coneable

多重视图界定

T <% Comparable[T] <% String

多重上下文界定

T : Ordering : Manifest

类型约束

可用的约束有三种:

  • T =:= UT是否等于U
  • T <:< UTU的子类型。
  • T <%< UT是否能被隐式转换为U

定义只在特定条件下使用的方法

类型约束让程序员定义只有在特定条件下使用的方法。例:

scala> class Pair[T](val first: T, val second: T) {
     |   def smaller(implicit ev: T <:< Ordered[T]) =
     |     if (first < second) first else second
     | }
defined class Pair

可以虽然File没有混入Ordered[T],但因为smaller声明了隐式参数,所以还是可以 构造出Pair[File]

scala> import java.io.File
import java.io.File

scala> val p = new Pair(new File("."), new File(".."))
p: Pair[java.io.File] = Pair@be1fcc

但是如果调用smaller()方法,那就出错了:

scala> p.smaller // Error
<console>:11: error: Cannot prove that java.io.File <:< Ordered[java.io.File].
              p.smaller // Error
                ^

另一个例子:

Option有一个orNull方法:

scala> val friends = Map("Fred" -> "Barney")
friends: scala.collection.immutable.Map[String,String] = Map(Fred -> Barney)

scala> val friendOpt = friends.get("Wilma") // An Option[String]
friendOpt: Option[String] = None

scala> val friendOrNull = friendOpt.orNull // A String or null
friendOrNull: String = null

Java类对象如果为null表示没有值,但基本类型没有办法用null表示。

因为orNull的实现带有约束Null <:< A,所以可能实例化Option[Int],只要别对 这些实例使用orNull就可以了:

scala> val scores = Map("Fred" -> 42)
scores: scala.collection.immutable.Map[String,Int] = Map(Fred -> 42)

scala> val scoreOpt = scores.get("Fred") // An Option[Int]
scoreOpt: Option[Int] = Some(42)

scala> val scoreOrNull = scoreOpt.orNull // Error
<console>:9: error: Cannot prove that Null <:< Int.
       val scoreOrNull = scoreOpt.orNull // Error

改进类型推断

<:<能增加类型推断,比如对于参数的下界声明:

scala> def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
firstLast: [A, C <: Iterable[A]](it: C)(A, A)

调用时不能直接传入整数列表,因为实参类型[Nothing, List[Int]]不符合形参类型 [A, C <: Iterable[A]]

scala> firstLast(List(1, 2, 3)) // Error
<console>:9: error: inferred type arguments [Nothing,List[Int]] do not conform 
							to method firstLast's type parameter bounds [A,C <: Iterable[A]]
              firstLast(List(1, 2, 3)) // Error
              ^
<console>:9: error: type mismatch;
 found   : List[Int]
 required: C
              firstLast(List(1, 2, 3)) // Error

因为要在同一步中匹配类型A与类型C,仅根据List(1,2,3)无法判断出A的类型。 解决方案可以通过柯里化的方式先匹配C再匹配A

scala> def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) =
     |   (it.head, it.last)
firstLast: [A, C](it: C)(implicit ev: <:<[C,Iterable[A]])(A, A)

scala> firstLast(List(1, 2, 3)) // OK
res3: (Int, Int) = (1,3)

再看一个类似的例子,corresponds方法检查两个序列的成员是否一一对应:

scala> val a = Array("Hello", "Fred")
a: Array[String] = Array(Hello, Fred)

scala> val b = Array(5, 4)
b: Array[Int] = Array(5, 4)

scala> a.corresponds(b)(_.length == _)
res7: Boolean = true

corresponds方法的声明其实是这样:

def corresponds[B](that: Seq[B])(p: (A, B) => Boolean): Boolean

对于柯里化后的两个参数列表,要推断类型B

scala> a.corresponds(b)(_.length == _)

就相当于是:

Array("Hello", "Fred").corresponds(Array(5, 4))(_.length == _)
  • Array[A]对应前实例Array("Hello","Fred"),那类型A就是String
  • 形参Seq[B]得到的实参是Array(5, 4),那类型B就是Int
  • 确认了AB,那(A, B) => Boolean就是(String, Int) => Boolean

类型证明

如果firstLast()方法要返回一个对象的头和尾,最直接的逻辑是:

def firstLast[C](it: C) = (it.head, it.last)

但这样是错误的,因为不知道it的类型C是什么类型,很有可能它没有headlast 方法。

如果用T表示有headlast方法的类型(比如Iterable特质)。需要确保C类型 必须继承或是可以隐式转换为T

def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)

en就是类型证明对象,它的类型是C <:< Iterable[A],保证CIterable[A]的 类型(或是子类)或可以转换为Iterable[A]。这里不知道Iterable成员的类型是啥, 就用Iterable[A]来表示。

=:=<:<<%<其实是库中的类而不是语法特性,而且是带有隐式值的类。比如在 Predef对象中<:<的定义:

abstract class <:<[-From, +To] extends Function1[From,To]

object <:< {
	implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x }
}

类型<:<[-From, +To]继承自函数,参数-From逆变而返回值+To协变。这个函数的 功能可以理解为把From类型从转为To类型对象。

伴生对象中的方法conforms[A]是一个隐式的对象。提供了一个把类型From转为类型To 的默认实现:在这里,它假设To类型就是From类型或是它的子类。它直接用apply() 方法返回一个<:<[A,A]的实例,因为同一个类型即是自己的超类又是自己的子类,所以 返回的<:<[A,A]符合<:<[-From, +To]

现在回到之前取列表头尾的函数,我们用整数列表为参数:

scala> def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) =
     |   (it.head, it.last)
firstLast: [A, C](it: C)(implicit ev: <:<[C,Iterable[A]])(A, A)

scala> firstLast(List(1, 7, 2, 9))
res0: (Int, Int) = (1,9)

在这里编译器要验证的是implicit ev: List[Int] <:< Iterable[Int],会先查看在 伴生对象中是否有可以应用到List[Int] <:< Iterable[Int]的隐式对象。当找到:

implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x }

List代入类型A

def conforms[List] = new (List <:< List) { def apply(x: List) = x }

的返回类型List <:< List可以匹配到List <:< Iterable。因为<:<[-From, +To]中 参数-From逆变而返回值+To协变。

检查泛型隐式对象是否存在

在REPL环境中可以用implicitly函数检查泛型隐式对象是否存在:

scala> implicitly[String <:< AnyRef]
res1: <:<[String,AnyRef] = <function1>

scala> implicitly[AnyRef <:< String]
<console>:8: error: Cannot prove that AnyRef <:< String.
              implicitly[AnyRef <:< String]
                        ^

存在会返回函数,不存在就返回错误:

scala> implicitly[List[Int] <:< Iterable[Int]]
res4: <:<[List[Int],Iterable[Int]] = <function1>

scala> implicitly[List <:< Iterable]
<console>:8: error: type List takes type parameters
              implicitly[List <:< Iterable]
                         ^
<console>:8: error: type Iterable takes type parameters
              implicitly[List <:< Iterable]
                                  ^

implicitNotFount注解

作为是为了让报错时的信息更加有可读性:

@implicitNotFound(msg = "I am baffled why you give me ${From} when I want ${To}.")
abstract class <:<[-From, +To] extends Function1[From, To]

object <:< {
  implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x }
}

def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) =
  (it.head, it.last)

这样当出错时:

scala> firstLast("Fred")
<console>:23: error: I am baffled why you give me String when I want Iterable[A].
              firstLast("Fred")
                       ^

CanBuildFrom解读

定义

模拟Iterable特质中的map方法。先定义Iterator特质:

trait Iterator[E] {
  def next(): E
  def hasNext: Boolean
}

集合的构造器是Builder,把E类型的元素添加到缓存中去。返回的结果是一个集合, 类型用To表示:

trait Builder[-E, +To] {
  def +=(e: E): Unit
  def result(): To
}

CanBuildFrom[From, E, To]特质提供类型证明。它的apply()方法把From类型的实例 转为Builder

trait CanBuildFrom[-From, -E, +To] {
  def apply(): Builder[E, To]
}

这样就实现的FromTo的类型兼容。

然后是Iterable特质,有iterator()方法返回Iterator类型的迭代器,map()方法 执行映射操作:

trait Iterable[A, Repr] {
  def iterator(): Iterator[A]

  def map[B, That](f : (A) => B)
    (implicit bf: CanBuildFrom[Repr, B, That]): That = 
  {
    val builder = bf()
    val iter = iterator()
    while (iter.hasNext) builder += f(iter.next())
    builder.result
  }
}

map()方法的和第一个参数列表是映射的方法F,很好理解。

第二个参数列表中的类型参数Repr是展现类型,它可以选择合适的构造器工厂来创建如 rangeString之类的非常规集合。

在Scala类库中的Iterablemap()方法是被定义在TraversableLike[A, Repr]特质 中的。这样更加常用的Iterable就不用再带上Repr这个类型参数了。

归纳

  • map()方法的主要任务是创造一个目标类型That的构造器Builder
  • 迭代源集合,把每个元素传递给映射方法f,把f的返回值放到Builder
  • builder.result()方法返回目标类型的集合。

使用

每个集合实现都在伴生对象中提供一个隐式的CanBuildFrom对象。比如下面的 简单版的ArrayBuffer实现,注意数组类型要有上下文界定[E : Manifest]

class Buffer[E : Manifest] extends Iterable[E, Buffer[E]] 
    with Builder[E, Buffer[E]] {

  private var capacity = 10
  private var length = 0

  private var elems = new Array[E](capacity) 

  def iterator() = new Iterator[E] {
    private var i = 0
    def hasNext = i < length
    def next() = { i += 1; elems(i - 1) }
  }

  def +=(e: E) {
    if (length == capacity) {
      capacity = 2 * capacity
      val nelems = new Array[E](capacity) 
      for (i <- 0 until length) nelems(i) = elems(i)
      elems = nelems
    }
    elems(length) = e
    length += 1
  }

  def result() = this

}

object Buffer {

  implicit def canBuildFrom[E : Manifest] = 
		new CanBuildFrom[Buffer[_], E, Buffer[E]] {
    def apply() = new Buffer[E]
  }

}

Iterator()方法返回迭代器;+=()方法实现添加元素;canBuildFrom()方法把源类型 Buffer[_]集合转为成员类型为E的构造器Buffer[Manifest]

scala>   val names = new Buffer[String]
names: Buffer[String] = Buffer@114069b

scala>   names += "Fred"

scala>   names += "Linda"

scala>   val lengths = names.map(_.length)
lengths: Buffer[Int] = Buffer@4c27d525

scala>   lengths.map(println(_))
4
5
res2: Buffer[Unit] = Buffer@1b8f2e35

注意这里的Buffer类已经有一个+=()方法了,而且返回类型就是自己。所以可以用它 自己来混入Builder接口。

相对来说如果我们需要的是一个简化版的Range类型,那就要注意Range类的构造函数 并不会返回一个Range类型实例(当然也不应该这样返回)如:

scala> (1 to 10).map(x => x * x)
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 4, 9, 16, 25, 36, 4
9, 64, 81, 100)

返回的类型应该是一个序列而不是一个Range。在Scala的类库中Range是扩展自 IndexedSeq[Int],而IndexedSeq的伴生对象定义一个构造Vector的构造器。对于 我们的简化版Range来说,要提供一个Buffer作为其构造器:

class Range(val low: Int, val high: Int) extends Iterable[Int, Range] {

  def iterator() = new Iterator[Int] {
    private var i = low
    def hasNext = i <= high
    def next() = { i += 1; i - 1 }
  }  

}

object Range {

  implicit def canBuildFrom[E : Manifest] = 
		new CanBuildFrom[Range, E, Buffer[E]] {
			def apply() = new Buffer[E]
  }

}

注意构造器的类型为Buffer[E]

对于map方法中的CanBuildFrom隐式参数的定义:

implicit bf: CanBuildFrom[Repr, E, That]

来说Repr就是Range,这样隐式参数就可以看作:

implicit bf: CanBuildFrom[Range, E, That]

Range伴生对象的canBuildFrom[E]被调用产生的是:

CanBuildFrom[Range, E, Buffer[E]]

上面这个就是bf的类型,其apply方法将产出Buffer[E],用于构造结果。

总之,隐式参数CanBuildFrom[Repr, E, That]会定位到一个可以产出目标集合构造器的 工厂对象。这个工厂是定义在Repr伴生对象中的一个隐式值。

调用时:

scala> import scala.math._
import scala.math._

scala>   val res = new Range(1, 10).map(sqrt(_))
res: Buffer[Double] = Buffer@79111260

scala>   res.map(println(_))

依赖于其他类型的类型的类型

与上面的CanBuildFrom类似的,还有一个通过依赖其他类型的实现方案.

List[T]依赖于类型T生成一个特定类型的实例,如List[Int]。有时称这样的泛型 类型为类型构造器(type constructor)。不仅如此,Scala中还可以定义出依赖于其他 类型的类型的类型。

为了明白这样做的意义,我们用一个简化版的Iterable特质来说明:

trait Iterable[E] {
	def iterator(): Iterator[E]
	def map[F](f: (E) => F): Iterable[F]
}

如果有一个类实现该特质:

class Buffer[E] extends Iterable[E] {
	def iterator(): Iterator[E] = ....
	def map[F](f: (E) => f): Buffer[F] = ...
}

因为需要在Buffer类的map()方法中返回的是Buffer类型自己而不是Iterable, 所以为了Iterable特质中实现这个map()方法,我们必须用一个东西来代表Buffer[E] 或是其他的子类:

trait Iterable[E, C[_]] {
	def iterator(): Iterator[E]
	def build[F](): C[F]
	def map[F](f: (E) => F): Iterable[F]
}

这里的参数类型C[_]自己也是一个参数类型,所以像高阶函数一样Iterable成为了一个 高阶类型。

对于map()方法返回的类型并不一定和实例原来的类型一样,比如:Buffer类的map() 方法的返回类型也是Buffer;但是Range执行map()方法的结果通常不会也是一个 Range(比如可能是一个Buffer[F])。所以对于Range类型声明的可能是这样的:

class Range extends Iterable[Int, Buffer]

这里的Int, Buffer对应Iterable声明中的E, C[_],显然C[_]对应的是Buffer

现在因为map()方法返回的类可以是存放任何F类型的容器,所以我们需要一个类来表示 存放任何F类型的容器的类Container

trait Container[E] {
  def +=(e: E): Unit
}

它正好适合作为Iterablebuild()方法的返回类型:

trait Iterable[E, C[X] <: Container[X]] {
	def build[F](): C[F]
	...
}

这里限制的容器CContainer的内容必须是相同类型的。

这样就可以在Iterable中实现map()方法了:

trait Iterable[E, C[X] <: Container[X]] {
  def iterator(): Iterator[E]
  def build[F : Manifest](): C[F]
  
  def map[F : Manifest](f: (E) => F): C[F] = {
    val res = build[F]()
    val iter = iterator()
    while (iter.hasNext) res += f(iter.next())
    res
  }
}

这样子类中就不用实现map()方法了。

下面是Range类的定义:

// An iterable, but not a container
class Range(val low: Int, val high: Int) extends Iterable[Int, Buffer] {
  def iterator() = new Iterator[Int] {
    private var i = low
    def hasNext = i <= high
    def next() = { i += 1; i - 1 }
  }  
  def build[F : Manifest]() = new Buffer[F]
    // Produced collection need not be the same type
}

它只混入了Iterable接口:可以遍历内容,但不能添加内容。

Buffer则混入了IterableContainer

class Buffer[E : Manifest] extends Iterable[E, Buffer] with Container[E] {
  private var capacity = 10
  private var length = 0
  private var elems = new Array[E](capacity) // See note
  def iterator() = new Iterator[E] {
    private var i = 0
    def hasNext = i < length
    def next() = { i += 1; elems(i - 1) }
  }
  def build[F : Manifest]() = new Buffer[F]
  def +=(e: E) {
    if (length == capacity) {
      capacity = 2 * capacity
      val nelems = new Array[E](capacity) // See note
      for (i <- 0 until length) nelems(i) = elems(i)
      elems = nelems
    }
    elems(length) = e
    length += 1
  }
}

Manifest上下文界定是为了构造Array[E]所必须的,这和高等类型没有什么关系。

小结

这是一个典型的例子:Iterator依赖Container。但Container不是一个普通的类型, 而是一个制件类型的机制。

在实际应用中,Scala的Iterable并不是高级类型来实现的,而是用隐式转换实现的一个 对象用于构造目标集合。

存在类型

所有Java类型在Scala中都有对等的概念。一般的类型可以用同名的类型表示,如:

  • Java中的Pattern对应Scala里的Pattern
  • Java中的Iterator<Component>对应Scala里的Iterator[Component]

但是像是Java里的Iterator<?>Iterator<? extends Component>这样的通配符类型 或是Iterator这样没有参数的原始类型要用到一种额外的叫作「存在类型」的类型来表示。

存在类型是Scala语言所支持的特性,但实际上它的作用是用于从Scala访问Java类型。主要 用途是当Scala访问Java时能够理解编译器报错的信息。存在类型的通用形式如下:

  type forSome { declarations }

type是任意的Scala类型,declarations是一个抽象的valtype列表。这个定义 可以解读为:声明的变量和类型是存在但未知的,正如类中的抽象成员那样。这个类型进而 被允许引用这些声明的变量和类型,虽然编译器不知道具体是什么类。

看一个具体的例子,Java中的Iterator<?>可以在Scala中写为:

Iterator[T] forSome { type T }

相当于前面的类型通配符:

Iterator[_]

其实类型通配符就是存在类型的一个语法糖。

Java中的:

Iterator<? extends Component>

在Scala中写为存在类型结合指定上界和下界的方式:

Iterator[T] forSome { type T <: Component }

相当于前面的类型通配符:

Iterator[_ <: Component]

还可以有更加复杂的表示法,如:

map[T, U] forSome { type T; type U <: T }

还可以在forSome块中使用val声明因为val是可以有内部类的。以类型:

import scala.collection.mutable.ArrayBuffer

class Network {

  class Member(val name: String) {
    // ...
  }
  
  // ...
  
}

为例,可以用这样的形式:

m.Member forSome { val n: Network }

这里就完全等同于类型投影:

Network#member

但也会有更复杂的情况:

def process[M <: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2)

这个方法只接收同一实例子类的成员:

val chatter = new Network
val myFace = new Network
val fred = chatter.join("Fred")
val wilma = chatter.join("Wilma")
val barney = myFace.join("Barney")
process(fred, wilma) // Ok
process(fred, barney) // Error

存在类型对于对于简单的用例来说,可以当forSome不存在。虽然forSome语句中的类型 和值是未知的,Scala还是会检查程序是否完备。举例来说,对于以下的Java类:

  // This is a Java class with wildcards
  public class Wild {
    Collection<?> contents() {
      Collection<String> stuff = new Vector<String>();
      stuff.add("a");
      stuff.add("b");
      stuff.add("see");
      return stuff;
    }
  }

如果在Scala中访问这个类,会看到它有一个存在类型:

  scala> val contents = (new Wild).contents
  contents: java.util.Collection[?0] forSome { type ?0 } =
     [a, b, see]

要看这个集合里有多少元素,可以简单忽略存在定义部分,像平常一样调用size方法:

  scala> contents.size()
  res0: Int = 3

对于复杂的类型的情况,存在类型会显得笨拙一些。因为没有办法给存在类型命名。以创建 一个可变Scala类型为例,需要用contents的元素初始化它:

  import scala.collection.mutable.Set
  val iter = (new Wild).contents.iterator
  val set = Set.empty[???]     // what type goes here? 这里要用什么类型?
  while (iter.hasMore)
    set += iter.next()

第三行里没有办法给出Java集合里的元素类型名称,所以不能给出set方法的满足类型。 为了绕过此问题,应该考试如下两种技巧:

  1. 将存在的类型传入方法时,把类型参数从forSome语句移到方法的类型参数中。 在方法体内,可以用这个类型参数来指定本来在forSome语句中的类型。
  2. 不要从方法返回存在的类型,而是返回一个带有forSome语句中的每个类型的抽象成员 的对象(参见抽象对象一章)。

使用这两个技巧,之前的代码写成这个样子:

  import scala.collection.mutable.Set
  import java.util.Collection

  abstract class SetAndType {
    type Elem
    val set: Set[Elem]
  }

  def javaSet2ScalaSet[T](jset: Collection[T]): SetAndType = {
    val sset = Set.empty[T]  // now T can be named!

    val iter = jset.iterator
    while (iter.hasNext)
      sset += iter.next()

    return new SetAndType {
      type Elem = T
      val set = sset
    }
  }

综上所棕,对于Scala来说,用存在类型实现不如抽象成员实现更加方便。所以Scala中几乎 不用存在类型。

Scala类型小结

类型 语法  
类 或 特质 class C ... , trait C ...  
元组 (T1, ... , Tn)  
函数类型 (T1, ... , Tn) => T  
方法类型 (T1, ... , Tn)T 编译器内部使用
注解 T @A  
参数化类型 A[T1, ... , Tn]  
单例类型 value.type  
类型投影 O#I  
复合类型 T1 with T2 with ... with Tn { ... }  
中置类型 T1 A T2  
存在类型 T forSome { type and val }  

方法类型(T1, ... , Tn)T(与函数类型相比少了=>)一般只在编译器内部使用。 在REPL中输入函数类型:

scala> val triple = (x: Int) => 3 * x
triple: Int => Int = <function1>

而方法类型是这样的:

scala> def square(x: Int) = x * x
square: (x: Int)Int

可以在方法后面加上_可以转为函数类型:

scala> square _
res0: Int => Int = <function1>

家族多态

就是许多个相关的类,又要共用代码、又要保护类型安全,这个比较难搞。Java的事件处理 是一个典型的例子:

  • 有多个不同的事件(如:ActionEventChangeEvent等……)
  • 每个事件有单独的监听器接口(如:ActionListenerChangeListener等……)

为了设计一套管理监听器的通用机制,我们先用泛型类型,然后再切换到抽象类型。

Java中每个监听器接口有不同的方法对应事件:actionPerformedstateChangeditemStateChanged等。先把这些方法统一起来:

// Version 1: The event source is an Object

import scala.collection.mutable.ArrayBuffer
import java.awt.event.ActionEvent

trait Listener[E] {
  def occurred(e: E): Unit
}

事件源要有一个监听器的集合,和一个触发这些监听器的方法:

trait Source[E, L <: Listener[E]] {
  private val listeners = new ArrayBuffer[L]
  def add(l: L) { listeners += l }
  def remove(l: L) { listeners -= l }
  def fire(e: E) { for (l <- listeners) l.occurred(e) }
}

在这个基础上,以按钮事件ActionEvent为例,生成对应的监听器:

trait ActionListener extends Listener[ActionEvent]

Button类可以混入Source特质:

class Button extends Source[ActionEvent, ActionListener] {
  def click() {
    fire(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"))
  }
}

现在Button类不需要重复监听器管理代码,并且监听器的类型是安全的:只能给按钮加上 ActionEventChangeListener

调用:

scala> val b = new Button
b: Button = Button@b11fcc2

scala> b.add(new ActionListener {
     |   def occurred(e: ActionEvent) {
     |     println(e)
     |   }
     | })

scala> b.click()
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=click,when=0,modifiers=] on 
$line14.$read$$iw$$iw$$iw$Button@b11fcc2

根据Java中ActionEvent类的定义,它把事件源设置为this,但事件源的类型为 Object。这里可以用自身类型让它也是类型安全的:

trait Event[S] {
  var source: S = _
}

trait Listener[S, E <: Event[S]] {
  def occurred(e: E): Unit
}

trait Source[S, E <: Event[S], L <: Listener[S, E]] {
  this: S =>
  private val listeners = new ArrayBuffer[L]
  def add(l: L) { listeners += l }
  def remove(l: L) { listeners -= l }
  def fire(e: E) {
    e.source = this // Self-type needed here
    for (l <- listeners) l.occurred(e)
  }
}

自身类型this: S =>把事件源都设为this,不然this只能是某种Source,而不一定 是Event[S]所要求的类型。

定义按钮的例子:

class ButtonEvent extends Event[Button]

trait ButtonListener extends Listener[Button, ButtonEvent]

class Button extends Source[Button, ButtonEvent, ButtonListener] {
  def click() { fire(new ButtonEvent) }
}

调用:

val b = new Button
b.add(new ButtonListener {
  def occurred(e: ButtonEvent) {
    println(e + " from " + e.source)
  }
})
b.click()

这里的参数类型太多了,看起来不是很简洁。而且类型Button是循环依赖的。

如果用抽象类型的话,会好很多:

import scala.collection.mutable.ArrayBuffer
import java.awt.event.ActionEvent

trait ListenerSupport {
  type S <: Source
  type E <: Event
  type L <: Listener

  trait Event {
    var source: S = _
  }

  trait Listener {
    def occurred(e: E): Unit
  }

  trait Source {
    this: S =>

    private val listeners = new ArrayBuffer[L]
    def add(l: L) { listeners += l }
    def remove(l: L) { listeners -= l }
    def fire(e: E) {
      e.source = this
      for (l <- listeners) l.occurred(e)
    }
  }
}

这样也有限制:不能声明顶级类型。所以这里把所有的类型都放在一个ListenerSupport 类型里面。

然后定义按钮事件与按钮监听器时,就可以把定义包含在一个扩展该特质的模块当中:

object ButtonModule extends ListenerSupport {
  type S = Button
  type E = ButtonEvent
  type L = ButtonListener

  class ButtonEvent extends Event

  trait ButtonListener extends Listener

  class Button extends Source {
    def click() { fire(new ButtonEvent) }
  }
}

调用的例子,注意要使用时必须引入这个模块:

scala> import ButtonModule._
import ButtonModule._

scala> val b = new Button
b: ButtonModule.Button = ButtonModule$Button@48250355

scala>   b.add(new ButtonListener {
     |     def occurred(e: ButtonEvent) {
     |       println(e + " from " + e.source)
     |     }
     |   })

scala> b.click()
$line6.$read$$iw$$iw$ButtonModule$ButtonEvent@65d0e7e9 from 
$line6.$read$$iw$$iw$ButtonModule$Button@48250355

注意:虽然这里类型名只用了一个字母:

  type S = Button
  type E = ButtonEvent
  type L = ButtonListener

但实际上标识符都可以用,而且应该是具有可读性的:

  type SourceType = Button
  type EventType = ButtonEvent
  type ListenerType = ButtonListener