Scala recursive types

I'm just trying to plunge into a Scala type system. I came across a code that looks like this:

trait A extends Something {
  type X <: XLike

  trait XLike { this: X =>
    ....
    ....
  }
}

      

What does it mean? In what situations would I like to write such code? I know I am nested types, but what is the advantage of nesting types like this and using that to denote a nested type?

+3


source to share


1 answer


This trait A#XLike

cannot be moved internally A

:

val a = new A {}; import a._

scala> class KK extends XLike
<console>:21: error: illegal inheritance;
 self-type KK does not conform to a.XLike selftype a.X
       class KK extends XLike
                        ^

      

It can be mixed inside implementations A

:

trait B extends A {
    type X = XImpl      
    trait XImpl extends XLike { this: X => }
}

val b = new B {}; import b._

scala> class KK extends XImpl
defined class KK

      

Or even:

trait B extends A {type X = XLike}

val b = new B {}; import b._

scala> class KK extends XLike
defined class KK

      

Thus, you can choose which trait is required for mixing:

trait B extends A {
  type X = XImpl1      
  trait XImpl1 extends XLike { this: X => }
  trait XImpl2 extends XLike { this: X => }
}

val b = new B {}; import b._

scala> class ZZ extends XImpl1
defined class ZZ

scala> class ZZ extends XImpl2
<console>:40: error: illegal inheritance;
 self-type ZZ does not conform to b.XImpl2 selftype b.XImpl2 with b.X
       class ZZ extends XImpl2
                        ^
scala> class ZZ extends XImpl1 with XImpl2 // XImpl2 still can be mixed but only if XImpl1 present in linearization
defined class ZZ

      

Even a few:



trait A {
   type X1 <: XLike
   type X2 <: XLike

   trait XLike { this: X1 with X2 => }
} 

trait B extends A {
    type X1 = XImpl1  
    type X2 = XImpl2

    trait XImpl1 extends XLike { this: X1 with X2 => }
    trait XImpl2 extends XLike { this: X1 with X2 => }
    trait XImpl3 extends XLike { this: X1 with X2 => }
}

      

In practice, X1

u X2

can have some important roles. For example, if you chaining resposibilty ( stackable traits ) - you can mix many traits (and not all of them explicitly - there may be groups of traits) in any order, so it's good to be able to specify one or more required handlers (just to don't forget), for example:

 val a = new Logic with Hanler with Group0
 trait Group0 extends MandatoryHandler1 with Group1 with H2
 trait Group1 extends H3 with H4 with MandatoryHandler2
 trait Group2 extends H2 with H5

      

If you change Group0

to Group2

, you might lose yours MandatoryHandler2

(for example, it might be some reporter).

One more: the library developer can provide many traits that will be concise with the Single Resposibilty / Interface Segregation principles , but some of them should always be mixed in (together) by the users who will play with this LEGO.

So, here is the classic OOP composition by aggregation method (instances required):

 abstract class B {
    val helper1: H1 //mandatory, exactly H1, not some H
    val helper2: H2 
 }

 class C extends B {
    val helper1 = new H1 //mandatory, exactly H1, not some H
    val helper2 = new H2 
 }
 class H1 extends H {...} 
 class H2 extends H {...}    

      

vs mix-in way:

 trait B extends A {
   type helper1 = H1
   type helper2 = H2

   trait H1 extends H // no abstract members here just mix-in
   trait H2 extends H
 }

 trait C extends H1 with H2 

      

+6


source







All Articles