首页 文章

自我类型和特质子类有什么区别?

提问于
浏览
366

特征的自我类型 A

trait B
trait A { this: B => }

说“ A 不能混合到一个不扩展 B ”的具体类中 .

另一方面,以下内容:

trait B
trait A extends B

说“在 A 中混合的任何(具体的或抽象的)类也将在B中混合” .

这两个陈述不是同一个意思吗?自我类型似乎只是为了创建一个简单的编译时错误的可能性 .

我错过了什么?

11 回答

  • 4

    自我类型允许您指定允许混合特征的类型 . 例如,如果您有一个自我类型 Closeable 的特征,那么该特征知道允许混合它的唯一内容必须实现 Closeable 接口 .

  • 0

    它主要用于Dependency Injection,例如蛋糕模式 . 在Scala中存在一个涵盖许多不同形式的依赖注入的great article,包括Cake Pattern . 如果您使用Google "Cake Pattern and Scala",您将获得许多链接,包括演示文稿和视频 . 目前,这是一个指向another question的链接 .

    现在,关于自我类型和扩展特征之间的区别是什么,这很简单 . 如果你说 B extends A ,则 BA . 当您使用自我类型时, B 需要 A . 使用自我类型创建了两个特定要求:

    • 如果 B 被扩展,那么你需要混入一个 A .

    • 当具体类最终扩展/混合这些特征时,某些类/特征必须实现 A .

    请考虑以下示例:

    scala> trait User { def name: String }
    defined trait User
    
    scala> trait Tweeter {
         |   user: User =>
         |   def tweet(msg: String) = println(s"$name: $msg")
         | }
    defined trait Tweeter
    
    scala> trait Wrong extends Tweeter {
         |   def noCanDo = name
         | }
    <console>:9: error: illegal inheritance;
     self-type Wrong does not conform to Tweeter's selftype Tweeter with User
           trait Wrong extends Tweeter {
                               ^
    <console>:10: error: not found: value name
             def noCanDo = name
                           ^
    

    如果 TweeterUser 的子类,则不会有错误 . 在上面的代码中,每当使用 Tweeter 时我们都需要 User ,但 User 没有提供给 Wrong ,所以我们得到了一个错误 . 现在,上面的代码仍在范围内,请考虑:

    scala> trait DummyUser extends User {
         |   override def name: String = "foo"
         | }
    defined trait DummyUser
    
    scala> trait Right extends Tweeter with User {
         |   val canDo = name
         | }
    defined trait Right 
    
    scala> trait RightAgain extends Tweeter with DummyUser {
         |   val canDo = name
         | }
    defined trait RightAgain
    

    使用 Right ,满足混合 User 的要求 . 但是,上面提到的第二个要求并不满足:对于扩展 Right 的类/特征,实现 User 的负担仍然存在 .

    使用 RightAgain 满足两个要求 . 提供了 UserUser 的实现 .

    有关更多实际用例,请参阅本答案开头的链接!但是,希望现在你明白了 .

  • 149

    自我类型允许您定义循环依赖项 . 例如,您可以实现此目的:

    trait A { self: B => }
    trait B { self: A => }
    

    使用 extends 继承不允许这样做 . 尝试:

    trait A extends B
    trait B extends A
    error:  illegal cyclic reference involving trait A
    

    在Odersky的书中,请参阅第33.5节(创建电子表格UI章节),其中提到:

    在电子表格示例中,类Model继承自Evaluator,因此可以访问其评估方法 . 换句话说,类Evaluator将其自身类型定义为Model,如下所示:

    package org.stairwaybook.scells
    trait Evaluator { this: Model => ...
    

    希望这可以帮助 .

  • 1

    另一个区别是自我类型可以指定非类型 . 例如

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

    这里的自我类型是结构类型 . 结果是,在Foo中混合的任何东西都必须实现一个无法“关闭”的方法返回单元 . 这允许安全混合用于鸭子打字 .

  • 11

    Martin Odersky的原始Scala论文的第2.3节"Selftype Annotations" Scalable Component Abstractions实际上解释了selftype超出mixin组合的目的:提供一种将类与抽象类型相关联的替代方法 .

    本文给出的例子如下,它似乎没有一个优雅的子类通讯员:

    abstract class Graph {
      type Node <: BaseNode;
      class BaseNode {
        self: Node =>
        def connectWith(n: Node): Edge =
          new Edge(self, n);
      }
      class Edge(from: Node, to: Node) {
        def source() = from;
        def target() = to;
      }
    }
    
    class LabeledGraph extends Graph {
      class Node(label: String) extends BaseNode {
        def getLabel: String = label;
        def self: Node = this;
      }
    }
    
  • 53

    另一件事没有提到:因为自我类型不是所需类的层次结构的一部分,所以它们可以从模式匹配中排除,特别是当您与密封的层次结构进行详尽匹配时 . 当您想要建模正交行为时,这很方便,例如:

    sealed trait Person
    trait Student extends Person
    trait Teacher extends Person
    trait Adult { this : Person => } // orthogonal to its condition
    
    val p : Person = new Student {}
    p match {
      case s : Student => println("a student")
      case t : Teacher => println("a teacher")
    } // that's it we're exhaustive
    
  • 11

    让我们从周期性依赖开始吧 .

    trait A {
      selfA: B =>
      def fa: Int }
    
    trait B {
      selfB: A =>
      def fb: String }
    

    但是,此解决方案的模块化并不像它首次出现的那么大,因为您可以覆盖自我类型:

    trait A1 extends A {
      selfA1: B =>
      override def fb = "B's String" }
    trait B1 extends B {
      selfB1: A =>
      override def fa = "A's String" }
    val myObj = new A1 with B1
    

    但是,如果覆盖自身类型的成员,则会失去对原始成员的访问权限,但仍可通过超级继承访问该成员 . 那么使用继承真正获得的是:

    trait AB {
      def fa: String
      def fb: String }
    trait A1 extends AB
    { override def fa = "A's String" }        
    trait B1 extends AB
    { override def fb = "B's String" }    
    val myObj = new A1 with B1
    

    现在我无法理解蛋糕模式的所有微妙之处,但令我印象深刻的是,强制模块化的主要方法是通过组合而不是继承或自我类型 .

    继承版本较短,但我更喜欢继承而不是自我类型的主要原因是我发现使用自我类型使初始化顺序正确更加棘手 . 但是,对于自我类型,您可以做一些事情你不能继承 . 自我类型可以使用类型,而继承需要特征或类,如:

    trait Outer
    { type T1 }     
    trait S1
    { selfS1: Outer#T1 => } //Not possible with inheritance.
    

    你甚至可以这样做:

    trait TypeBuster
    { this: Int with String => }
    

    虽然你永远无法实例化它 . 我没有看到任何绝对的理由不能从类型继承,但我当然觉得有路径构造函数类和特性是有用的,因为我们有类型构造函数traits / classes . 不幸的是

    trait InnerA extends Outer#Inner //Doesn't compile
    

    我们有这个:

    trait Outer
    { trait Inner }
    trait OuterA extends Outer
    { trait InnerA extends Inner }
    trait OuterB extends Outer
    { trait InnerB extends Inner }
    trait OuterFinal extends OuterA with OuterB
    { val myV = new InnerA with InnerB }
    

    或这个:

    trait Outer
      { trait Inner }     
      trait InnerA
      {this: Outer#Inner =>}
      trait InnerB
      {this: Outer#Inner =>}
      trait OuterFinal extends Outer
      { val myVal = new InnerA with InnerB with Inner }
    

    应该更加强调的一点是,特征可以扩展类 . 感谢David Maclver指出这一点 . 这是我自己的代码中的一个例子:

    class ScnBase extends Frame
    abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
    { val geomR = geomRI }    
    trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
    trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
    

    ScnBase 继承自Swing Frame类,因此它可以用作自我类型,然后在最后混合(在实例化时) . 但是, val geomR 需要在继承特征之前初始化 . 所以我们需要一个类来强制事先初始化 geomR . 然后,类 ScnVista 可以从多个正交特征继承,这些特征本身可以从中继承 . 使用多种类型参数(泛型)提供了另一种模块化形式 .

  • 254

    TL; DR摘要其他答案:

    • 您扩展的类型是暴露给继承的类型,但自我类型不是

    例如: class Cow { this: FourStomachs } 允许您使用仅适用于反刍动物的方法,例如 digestGrass . 然而,扩展Cow的特征将没有这样的特权 . 另一方面, class Cow extends FourStomachs 会将 digestGrass 暴露给 extends Cow 的任何人 .

    • 自我类型允许循环依赖,而不扩展其他类型
  • 9
    trait A { def x = 1 }
    trait B extends A { override def x = super.x * 5 }
    trait C1 extends B { override def x = 2 }
    trait C2 extends A { this: B => override def x = 2}
    
    // 1.
    println((new C1 with B).x) // 2
    println((new C2 with B).x) // 10
    
    // 2.
    trait X {
      type SomeA <: A
      trait Inner1 { this: SomeA => } // compiles ok
      trait Inner2 extends SomeA {} // doesn't compile
    }
    
  • 9

    Update: 一个主要的区别是自我类型可能依赖于多个类(我承认这有点像一个角落) . 例如,你可以拥有

    class Person {
      //...
      def name: String = "...";
    }
    
    class Expense {
      def cost: Int = 123;
    }
    
    trait Employee {
      this: Person with Expense =>
      // ...
    
      def roomNo: Int;
    
      def officeLabel: String = name + "/" + roomNo;
    }
    

    这允许将 Employee mixin添加到 PersonExpense 的子类的任何内容中 . 当然,只有 Expense 扩展 Person 或反之亦然,这才有意义 . 关键是使用自我类型 Employee 可以独立于它所依赖的类的层次结构 . 它不关心什么扩展 - 如果切换 Expense vs Person 的层次结构,则不必修改 Employee .

  • 7

    在第一种情况下,B的子特征或子类可以混合到任何用途A.所以B可以是抽象特征 .

相关问题