理解 Scala 的 self type

2016-10-08

什么是 self type

有这样一个场景,我们有两个特质 A 和 B,在特质 A 中我们需要使用到特质 B 的数据。如下面的例子中,特质 doGreeting 需要访问特质 GreetingProvider 的成员:

trait GreetingProvider{
  val greeting = "Hello, "
}

trait doGreeting{
  def printGreeting(name: String): Unit =
    println(greeting + name)
}

class Greeting extends doGreeting with GreetingProvider

object Main extends App{
  val t = new Greeting
  t.printGreeting("world")
}

如果我们编译上述代码,那么编译器就会抱怨找不到 greeting,这是因为 greeting 定义在 GreetingProvider 中, doGreeting 无法直接访问。对于这种问题,Scala 提供了 self type 来解决。使用 self type, 我们可以将 doGreeting 的定义修改为:

trait doGreeting{
    this: GreetingProvider =>  // reassign this
    
    def printGreeting(name: String): Unit =
      println(greeting + name)
}

重新编译上述代码,就会输出我们想要的结果:

Hello,world

注意,类 Greeting 继承 GreetingProvider 时还需要混入(mixin)特质 doGreeting,这是因为 GreetingProvider 依赖于 doGreeting。如果不混入,将导致编译错误:

Error:(16, 24) illegal inheritance;
 self-type Greeting does not conform to doGreeting's selftype doGreeting with GreetingProvider
class Greeting extends doGreeting

使用 self type 还可以混入不止一个特质,如下:

this: Foor with Bar with Baz => // need keyword with

对应地,继承或者实例化时也要混入相应特质。

那么,什么是 self-type 呢?Programming in Scala Chapter 29 解释如下:

  • Technically, a self type is an assumed type for this whenever this is mentioned within the class.
  • Pragmatically, a self type specifies the requirements on any concrete class the trait is mixed into. If you have a trait that is only ever used when mixed in with another trait or traits, then you can specify that those other traits should be assumed.

为什么不使用继承

如果一个特质需要另一个特质的数据或者方法,还可以使用继承,为什么需要 self type 呢?这是因为,继承定义了 is a 关系;self type 则定义了 use a 关系。 下面是一个例子:

trait Base {
  def magic: String
}

trait Extender extends Base {
  def myMethod = "I can " + magic
}

trait SelfTyper {
  self: Base =>

  def myMethod = "I can " + magic
}

class A extends SelfTyper with Base {
  override def magic: String = "fly"
}

class B extends Extender {
  override def magic: String = "fly"
}

乍一看,继承和 self type 并没有什么不同:类 A 继承 SelfTyper 还混入了 Base,那么需要实现抽象方法 magic;类 B 直接继承 Extender,也需要实现抽象方法 magic。

但是类继承 SelfTyper 时,除了直接混入 Base,还可以混入一个继承 Base ,并实现了 magic 的特质:

trait DummyBase extends Base {
  override def magic: String = "boom"
}

trait AnotherDummyBase extends Base {
  override def magic: String = "boom again"
}

class C extends SelfTyper with DummyBase

class D extends SelfTyper with AnotherDummyBase

这样的话,类就不再需要实现抽象方法了。

除此之外,self type 还可以用于定义 structural type:

trait Foo {
  this: {def close: Unit} =>
}

这样,最终混入 Foo 的类必须实现 close 方法。

self type 另一个应用是实现依赖注入,可参考:real-world scala: dependency injection (di)

this 别名

如果使用 self type 不声明类型,此时 self 就是 this 别名,一个用法是在内部类使用 self 引用外部变量:

class Outer { 
    self =>
    val x = 1
    class Inner {
        println(self.x) // equivalent to Outer.v1
    }
}

总结

总结一下 self type 含义和用法 :

  1. self type 可解决特质间的依赖关系。当一个特质依赖其他特质的数据或者方法, 可以使用 self type。
  2. 一个特质使用 self type, 可以混入不止一个特质
  3. 如果一个特质使用了 self type, 那么混入这个特质时, 需要混入它依赖的其他特质
  4. self type 不声明类型,还可以作为 this 别名使用

参考:

  1. Programming in Scala
  2. Understanding the self type annotation and how it relates to the Cake Pattern
  3. What is the difference between self-types and trait subclasses
  4. scala类型系统:9) this别名&自身类型