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?
source to share
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
source to share