<
Scala 类型系统04:复合类型与存在类型
>
上一篇

一见倾心的 Scala I/O 开源库:Better-Files(官方文档翻译)
下一篇

Scala 类型系统03:自类型标记与结构化类型

当你声明一个组合了若干种类型的实例时,你就得到了复合类型(compound type)

存在类型(existential type)是对类型做抽象的一种方法。他可以让你在不知道具体类型的情况下就断言该类型 “存在”。通常你不知道该类型是什么,或者当前上下文中你并不需要知道这个类型。

1 复合类型

当你声明一个组合了若干种类型的实例时,你就得到了复合类型(compound type)

trait T1
trait T2
class C
// 声明复合类型
val c = new C with T1 with T2
// 相当于在声明时手动构造了一条继承链,c 同时被视为 T1,T2,C 的实例
val t1: T1 = c
val t2: T2 = c
val c2: C  = c

类型细化(type refinement )是复合类型的附加部分,相当于 Java 中的 new 接口/抽象类 {} 实例,并手动添加实现已形成新的类型,同时,JVM 会在字节码中为它合成一个唯一的名字。

相比之下,Scala 更进一步,他会合成新类型,并用新类型反映我们添加的东西。

例如:

scala> val subject = new Subject {
  |  type State = Int
  |  protected var count = 0
  |  def increment(): Unit = {
  |    count += 1
  |    notifyObservers(count)
  |  }
  |}

subject: TypeSystem.structuraltypes.Subject{type State = Int; def increment(): Unit} = $anon$1@4e3d11db

类似的,我们也可以同时声明复合类型:

scala> trait Logging {
|    def log(message: String): Unit = println(s"Log: $message") 
|  }

scala> val subject = new Subject with Logging {...} 

subject: TypeSystem.structuraltypes.Subject with Logging{type State = Int; def increment(): Unit} = $anon$1@8b5d08e

此时为了从外部访问细化后的特性或成员,你需要使用反射 API。

2 存在类型

存在类型(existential type)是对类型做抽象的一种方法。他可以让你在不知道具体类型的情况下就断言该类型 “存在”。通常你不知道该类型是什么,或者当前上下文中你并不需要知道这个类型。

在以下三种场景中,存在类型对 Scala 与 Java 的类型兼容来说尤为重要:

假设现在需要对 Seq[Int] 和 Seq[String] 分别处理,令每项翻倍:

object EveryDouble {
  def handle(seq: Seq[String]): Seq[Int] = handle(seq.map.(_.toInt))
  def handle(seq: Seq[Int]): Seq[Int] = seq.map(_*2)
}

但是如果这样写的话,会得到一个编译错误:“在类型查出后,方法的类型相同” (have the same type after erasure) (因为 JVM 的类型擦除)

你可以使用如下繁琐的方式解决这个问题:

 object EveryDouble {
  def handle(seq: Seq[_]): Seq[Int] = seq match {
    case Nil => Nil
    case head +: tail => (toInt(head) * 2) +: handle(tail)
  }
  private def toInt(x: Any): Int = x match {
    case i: Int => i
    case s: String => s.toInt
    case x => throw new RuntimeException(s"Unexpected list element $x")
	} 
}

表达式 Seq[_] 是存在类型Seq[T] forSome { type T }的 简写,表示 Seq 中元素的类型可以是任意类型,也通过类型边界进行控制:

简写 完整形式 描述
Seq[_] Seq[T] forSome {type T} T 可以是 Any 的任意子类
Seq[_ <: A] Seq[T] forSome {type T <: A} T 可以是 A (在某处已经定义了)的任意子类
Seq[_ >: Z <: A] Seq[T] forSome {type T >: Z <: A} T 可以是 A 的子类且是Z的超类

如果考虑过 Scala 的泛型语法与 Java 的语法如何对应,你可能已经注意到类似 java.util.List[_ <: A]的表达式在结构上与Java的表达式 java.util.List<? extends A> 很像。 事实上它们确实是同一个声明。尽管我们说 Scala 的变异行为在声明时就已经定义了,但你可以用存在类型定义出调用时才确定的变异行为,只是通常很少这么做。

所以在 Scala 代码中会经常看到类似 Seq[_] 的代码,其中类型参数不能被更具体地指定。但不会经常看到完整的 forSome 形式的存在类型语法。

存在类型存在的主要目的是为了支持 Java 泛型,同时保持它在 Scala 类型系统中的正确 性。大多数情况下,类型推断已经为我们隐藏了细节。

Top
Foot