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

后端 未结 11 1988
名媛妹妹
名媛妹妹 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:31

    Another thing that has not been mentioned: because self-types aren't part of the hierarchy of the required class they can be excluded from pattern matching, especially when you are exhaustively matching against a sealed hierarchy. This is convenient when you want to model orthogonal behaviors such as:

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

    in the first case, a sub-trait or sub-class of B can be mixed in to whatever uses A. So B can be an abstract trait.

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

    Self types allow you to define cyclical dependencies. For example, you can achieve this:

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

    Inheritance using extends does not allow that. Try:

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

    In the Odersky book, look at section 33.5 (Creating spreadsheet UI chapter) where it mentions:

    In the spreadsheet example, class Model inherits from Evaluator and thus gains access to its evaluation method. To go the other way, class Evaluator defines its self type to be Model, like this:

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

    Hope this helps.

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

    Let's start with the cyclical dependency.

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

    However, the modularity of this solution is not as great as it might first appear, because you can override self types as so:

    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
    

    Although, if you override a member of a self type, you lose access to the original member, which can still be accessed through super using inheritance. So what is really gained over using inheritance is:

    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
    

    Now I can't claim to understand all the subtleties of the cake pattern, but it strikes me that the main method of enforcing modularity is through composition rather than inheritance or self types.

    The inheritance version is shorter, but the main reason I prefer inheritance over self types is that I find it much more tricky to get the initialisation order correct with self types. However, there are some things you can do with self types that you can't do with inheritance. Self types can use a type while inheritance requires a trait or a class as in:

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

    You can even do:

    trait TypeBuster
    { this: Int with String => }
    

    Although you'll never be able to instantiate it. I don't see any absolute reason for not being be able to inherit from a type, but I certainly feel it would be useful to have path constructor classes and traits as we have type constructor traits / classes. As unfortunately

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

    We have this:

    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 }
    

    Or this:

      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 }
    

    One point that should be empathised more is that traits can extends classes. Thanks to David Maclver for pointing this out. Here's an example from my own code:

    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 inherits from the Swing Frame class, so it could be used as a self type and then mixed in at the end (at instantiation). However, val geomR needs to be initialised before it's used by inheriting traits. So we need a class to enforce prior initialisation of geomR. The class ScnVista can then be inherited from by multiple orthogonal traits which can themselves be inherited from. Using multiple type parameters (generics) offers an alternative form of modularity.

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

    A self type lets you specify what types are allowed to mixin a trait. For example, if you have a trait with a self type Closeable, then that trait knows that the only things that are allowed to mix it in, must implement the Closeable interface.

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