<
Scala 类型系统03:自类型标记与结构化类型
>
上一篇

Scala 类型系统04:复合类型与存在类型
下一篇

Scala 类型系统02:抽象类型

我们可以用 this 来指代调用该方法的实例。一般情况下,我们不需要显示地使用 this,但如果作用域内存在同名变量时,显示使用 this 有助于消除二义性。此时就需要使用自类型标记 (self-type annotation )

而有时我们想处理任意带有某方法的实例,此时就要用到结构化类型(Structural type)

1 自类型标记

自类型标记 (self-type annotation )可以帮助我们完成两件事:

先来看第一点,自类型标记可以起到为 this 起别名的功能:

// 下文的 self v 并不是关键字,可以把它换成任何合法的关键字
class C1 { self =>
  def talk(message: String) = println(s"C1 talk: $message")
  class C2 {
    class C3 {
      def talk(message: String) = self.talk(s"C3 talk: $message")
    }
  }
}

val c1 = new C1
cl.talk("Hello")       // C1 talk: Hello    
cl.c2.c3.talk("Hello") // C1.talk: C3.talk: World

如果没有自类型标记,我们就无法直接从 C3.talk 中调用 C1 talk,因为二者名称相同,后者将屏蔽前者,而且 C3 也不是 C1 的直接子类,所以也不能调用 super.talk。

可以将其认为是一个指向固定位置的 this 引用

功能二:声明某特征/类必须引入另一特征/类

trait User {
  def username: String
}

trait Tweeter {
  // 注意这一行
  // 这一行将 this 的类型指定为 User
  // 此时这个 trait 相当于变成了 User 的子特征,可以使用 User 中的元素了
  this: User =>
  def tweet(tweetText: String) = println(s"$username: $tweetText")
}

// 但同时,使用 Tweeter 这个特征就必须同时引入 User 这个特征
class VerifiedTweeter(val username_ : String) extends Tweeter with User {
  def username = s"real $username_"
}

val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade")  // prints "real Beyoncé: Just spilled my glass of lemonade"

自类型标记可以将一个特征/类 A 指定为另一个特征/类 B 的子特征/类,从而使用 B 中对应的功能,但引入这个被修改的特征/类时就必须同时引入 B 类。同理,此处的 this 关键字也可以替换成其他名称。

2 结构化类型

“如果它走起来像鸭子,并且跟鸭子一样呱呱叫,那么它一定是鸭子。” —— 鸭子类型

结构化类型(Structural type)可以当做一种类型安全的鸭子类型(duck typing)。鸭子类型是动态类型语言中一种方法解析方式,例如:在 Ruby 中如果你的代码包含有列表相关的操作时,即使他不是真正的列表,也能被当做列表使用。换句话说,解释器并不知道这个实例是否是列表的实例,但他会遵循各种规则来寻找方法以供调用,或在找不到方法时对失败进行处理。

Scala 不支持这种运行时的方法解析(虽然有一个例外待补充),不过,Scala 有一个类型的机制,他在编译时进行处理。Scala 允许你指定的对象必须符合某种特定的结构:

示例代码如下:

object ScJs {

  // 像这样指定类型
  def letsQuack(duck: {def quack(): String}): Unit = {
    println(duck.quack())
  }

  // 以3个不同的实现类为例
  class SimpleDuck {
    def quack(): String = "Quack"
  }

  class MachineDuck {
    def quack(): String = "q-u-a-c-k"
  }

  // 假设这是一个只有 quack 方法实现,而其他完全不同的类
  class Synthesizer {
    def quack(): String = "Output> quack"
  }

  def main(args: Array[String]): Unit = {
    letsQuack(new SimpleDuck)  // Quack
    letsQuack(new MachineDuck) // q-u-a-c-k
    letsQuack(new Synthesizer) // Output> quack
  }
}

即使类 Synthesizer 已经与鸭子毫无关系了(举例),我们依然可以正常执行方法并获得输出。我们在指定抽象类型时,一样可采用这种声明方式。

  trait QuackLike {
    // 因为下文缘故,此处必须开启反射调用
    import scala.language.reflectiveCalls
    type Sound
    // 不过由于 Scala 不会让结构化类型 指向抽象类型或类型参数,所以下文中的 state 必须指定一个已经存在的类型
    // (意为:虽然你能嘎嘎叫,但你可能不是鸭子,因此我无法执行除了嘎嘎叫外的其他方法,需要强制指明这是鸭子之后才可以)
    type quack = {def quack(sound: Any): String}
    type quack = {def quack(sound: Sound): String} // ❌编译错误
    
    // 测试代码
    def letsQuack(duck: quack, sound: Sound): Unit = {
      println(duck.quack(sound))
    }
  }

trait QuackLike {
    type Sound
    // 这是另一种声明方式,将需要调用的方法起别名,这样在调用时只需要关心方法而不需要关心类型
    // 在你只关心方法时这种更加实用,而且函数的类型也不需要强行指定为 Any 了
    type quack = Sound => String
  
    def letsQuack(quack: quack, sound: Sound): Unit = {
      println(quack(sound))
    }
  }
Top
Foot