What is the difference between self-types and trait subclasses?

后端 未结 11 1987
名媛妹妹
名媛妹妹 2020-11-22 08:53

A self-type for a trait A:

trait B
trait A { this: B => }

says that \"A cannot be mixed into a concrete cl

相关标签:
11条回答
  • 2020-11-22 09:21

    Section 2.3 "Selftype Annotations" of Martin Odersky's original Scala paper Scalable Component Abstractions actually explains the purpose of selftype beyond mixin composition very well: provide an alternative way of associating a class with an abstract type.

    The example given in the paper was like the following, and it doesn't seem to have an elegant subclass correspondent:

    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;
      }
    }
    
    0 讨论(0)
  • 2020-11-22 09:21

    TL;DR summary of the other answers:

    • Types you extend are exposed to inherited types, but self-types are not

      eg: class Cow { this: FourStomachs } allows you to use methods only available to ruminants, such as digestGrass. Traits that extend Cow however will have no such privileges. On the other hand, class Cow extends FourStomachs will expose digestGrass to anyone who extends Cow .

    • self-types allow cyclical dependencies, extending other types does not

    0 讨论(0)
  • 2020-11-22 09:22

    One additional difference is that self-types can specify non-class types. For instance

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

    The self type here is a structural type. The effect is to say that anything that mixes in Foo must implement a no-arg "close" method returning unit. This allows for safe mixins for duck-typing.

    0 讨论(0)
  • 2020-11-22 09:24
    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
    }
    
    0 讨论(0)
  • 2020-11-22 09:27

    It is predominately used for Dependency Injection, such as in the Cake Pattern. There exists a great article covering many different forms of dependency injection in Scala, including the Cake Pattern. If you Google "Cake Pattern and Scala", you'll get many links, including presentations and videos. For now, here is a link to another question.

    Now, as to what is the difference between a self type and extending a trait, that is simple. If you say B extends A, then B is an A. When you use self-types, B requires an A. There are two specific requirements that are created with self-types:

    1. If B is extended, then you're required to mix-in an A.
    2. When a concrete class finally extends/mixes-in these traits, some class/trait must implement A.

    Consider the following examples:

    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
                           ^
    

    If Tweeter was a subclass of User, there would be no error. In the code above, we required a User whenever Tweeter is used, however a User wasn't provided to Wrong, so we got an error. Now, with the code above still in scope, consider:

    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
    

    With Right, the requirement to mix-in a User is satisfied. However, the second requirement mentioned above is not satisfied: the burden of implementing User still remains for classes/traits which extend Right.

    With RightAgain both requirements are satisfied. A User and an implementation of User are provided.

    For more practical use cases, please see the links at the start of this answer! But, hopefully now you get it.

    0 讨论(0)
  • 2020-11-22 09:30

    Update: A principal difference is that self-types can depend on multiple classes (I admit that's a bit corner case). For example, you can have

    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;
    }
    

    This allows to add the Employee mixin just to anything that is a subclass of Person and Expense. Of course, this is only meaningful if Expense extends Person or vice versa. The point is that using self-types Employee can be independent of the hierarchy of the classes it depends on. It doesn't care of what extends what - If you switch the hierarchy of Expense vs Person, you don't have to modify Employee.

    0 讨论(0)
提交回复
热议问题