Scala 类型系统:单例类型

对于单例类型(singleton type),在《scala impatient》这本书(中文版:快学scala)里有提到过,当时读的时候,扫了一下,以为就是指scala里用object定义的这种单例对象呢,没有仔细看,最近才发现其实singleton type 是所有实例都可以有

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> object A
defined object A

scala> A.getClass
res0: Class[_ <: A.type] = class A$

scala> typeOf[A.type]
res1: reflect.runtime.universe.Type = A.type

对于这种单例,它的类型与它的类不同,要用 A.type 来表示。
这有点怪,通常我们不会用它,比如下面的方式都多此一举:

scala> val a : A.type = A
a: A.type = A$@b25b095

scala> def foo() : A.type = A
foo: ()A.type

一方面因为scala有类型推导的功能,另一方面,因为单例是唯一的,A.type类型只有唯一的实例A(排除null),我需要的话直接用A就好了。

不过我们讨论的话题重点是 singleton type,想象一下A是一个对象实例,是否对任何实例x都存在一个x.type这样的类型呢?

scala> class A

scala> val a = new A

scala> typeOf[a.type]
res0: reflect.runtime.universe.Type = a.type

wow,真的存在。再用这个类型声明一个变量看看:

scala> val x:a.type = a
x: a.type = A@6738694b

灵的,如果赋一个非a的实例呢?

scala> val x:a.type = a2
<console>:13: error: type mismatch;
found : a2.type (with underlying type A)
required: a.type

scala> typeOf[a.type] == typeOf[A] // a.type 与 A 不是同一个类型
res2: Boolean = false

scala> typeOf[a.type] == typeOf[a2.type] // a.type 与 a2.type 也不同
res1: Boolean = false

scala> typeOf[a.type] <:< typeOf[A] // a.type 是 A 类型的子类型
res5: Boolean = true

看到了,a.type 与 a2.type 是不同的类型!a.type也是单例类型,它也只有唯一的实例: a (排除null),说明,this.type是路径依赖的。

所有的对象实例都有一个x.type的单例类型,它只对应当前对象实例。这么做有什么意义呢?

在“链式”调用风格下,有适用的场景:

class A {def method1: A = this }
class B extends A {def method2: B = this}

val b = new B
b.method2.method1 // 可以工作
b.method1.method2 // 不行,提示:error: value method2 is not a member of A

有些人很喜欢用 x.foo.bar 这样的方式连续的去操作,这种风格也成为”链式调用”风格,它要求方法返回的必须是当前对象类型,以便连贯的调用方法。不过上面,因为父类中声明的method1方法返回类型限制死了就是A类型(不写返回值类型,用类型推导也一样),导致子类对象调用完method1之后,类型已经变成了父类型,无法再调用子类型中的方法了。解决方法是:

class A { def method1: this.type = this } 
class B extends A { def method2 : B = this }

val b = new B
b.method1.method2 // ok

主要原因就是:

class A { def method1 = this }

中 method1 返回类型为 A,没有写出来但编译器会推断为 A。确认方法是自己在这里填上 : A 看编译器报错不。所以 b.method1 返回的是 A 类型的对象,A 类型中没有 method2。

而带上 this.type 后:

class A { def method1: this.type = this }

this.type 是 method1 的返回值,这个返回值很特殊,得在运行期才会真正计算出具体类型,所以当对象实际类型是 B 的时候,调用 b.method1 返回的还是子类 B 类型,而不是 A 类型。这个就是 this type 的用途,是 scala 为了满足这类需求专门设计的关键字。

背后的原理:

简单解释一下。我们平时这么写 val a = new A ,我们会说a的类型是A。实际上,在编译这行代码时,编译器会自动生成一个类A的匿名子类(我们不妨把它称为A_$) ,然后用这个匿名子类A_$实例化(并且只会实例化)一个对象 a。我们把这个匿名子类称为single class, 因此对象a的真正类型应该是这个A_$的类型,我们把它叫做singe type。

那this.type有什么作用呢? 主要是在某些场合下加强类型约束,或者说是为了确保类型的绝对安全。

trait ActiveRecord {def entity: this.type = this} 

class Person extends ActiveRecord{
override def entity = new ActiveRecord(){} //编译错误,只能返回当前对象,即this
}

假如ActiveRecord的entity方法返回ActiveRecord类型, 那么实现类可以返回任意ActiveRecord类型的子类型。 因此将类型声明为this.type,可以对链式调用提供安全保障。 但这在Java中是无法做到的,除非把该方法声明为final,防止被子类改写,但这样一来就失去了灵活性。Play! 的ScaleModel类便运用了Scala的这种特性。

From: scala类型系统:3) 单例类型


- - - - - - - - End Thank For Your Reading - - - - - - - -