Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala recursive types

Tags:

types

scala

I'm just trying to get my head around Scala's type system. I stumbled upon a code that looks like this:

trait A extends Something {
  type X <: XLike

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

What would this mean? Under which situations would I want to code like this? I know that I'm nesting types, but what is the advantage of nesting types like this and using this to refer to the nested type?

like image 616
joesan Avatar asked May 29 '26 10:05

joesan


1 Answers

This A#XLike trait cannot be mixed anywhere inside 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's selftype a.X
       class KK extends XLike
                        ^

It can be mixed inside As implementations:

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

So it allows you to choose which trait is mandatory to mix-in:

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's 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 several ones:

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 and X2 might have some significant roles. For instance, if you do chain of resposibilty (stackable traits) - you may mix-in many traits (and not all of them explicitly - there might be groups of traits) in any order, so it's good to have ability to specify one or several mandatory handlers (just to not forget them), like:

 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 may loose your MandatoryHandler2 (it may be some reporter for example).

Another one: library developer may provide many traits to be concise with Single Resposibilty / Interface Segregation principle, but some of them should always be mixed in (together) by users who will play with this LEGO.

So here is classic OOP-composition over aggregation way (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 
like image 125
dk14 Avatar answered May 31 '26 05:05

dk14