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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With