How to mix Generics and Abstract Types in Scala?
I want to do the following, but "Iterable[BASE]" won't compile. What i开发者_如何学运维s the right way to do it, while keeping BASE an abstract type?
trait Base
trait Meta {
type BASE <: Base
}
trait EnumBase extends Base with Ordered[EnumBase]
trait EnumMeta extends Meta with Iterable[BASE] {
override type BASE <: EnumBase
}
trait Manager extends EnumMeta {
override type BASE <: MetaBase
}
I want that for every trait that extends EnumMeta and redefines BASE, that trait will be an Iterable of it's own BASE. So far I found I could instead do this:
trait EnumMeta extends Meta with Iterable[EnumBase] {
override type BASE <: EnumBase
}
trait Manager extends EnumMeta with Iterable[MetaBase] {
override type BASE <: MetaBase
}
Is this the only (repetitive) way, or is there a better way, as long as it doesn't require going back to the generics parameters?
[EDIT] I have just found that this will not work if the generic type parameter is the self-type, as in Ordered[EnumBase]. If you try to constrain it more in a derived class called MetaBase, you get:
illegal inheritance; self-type MetaBase does not conform to
Ordered[MetaBase]'s selftype Ordered[MetaBase]
As Iterable
is generic, there is a strong icentive to choose generics over type members, at least for EnumMeta and beyond. You can go from an ancestor with a type member to generics like this:
type EnumMeta[B <: Base] extends Meta with Iterable[B] {
override type BASE = B
}
Working with several traits whose relations involves type members, you may also consider putting type member in an enclosing context rather than in the class themselves, like this
trait Context {
type BASE <: Base
type META <: Meta
trait Base { def meta: META}
trait Meta { def base: BASE}
}
trait EnumContext extends Context {
type BASE <: EnumBase
type META <: EnumMeta
trait EnumBase extends Base with Ordered[EnumBase] {}
trait EnumMeta extends Meta with Iterable[B] {}
}
(if at top level, traits Base
or Meta
are empty, remove them and just have an abstract member type BASE
-and/or type META
- without constraints)
When you have a context you really want to use, you can either mix it in, or make an object extending it.
object EnumContext extends EnumContext {
type BASE = EnumBase
type META = EnumMeta
}
Another way to get your code to compile is with a type projection,
trait Base
trait Meta {
type BASE <: Base
}
trait EnumBase extends Base with Ordered[EnumBase]
trait EnumMeta extends Meta with Iterable[Meta#BASE] { // Type projection!
type BASE <: EnumBase
}
trait Manager extends EnumMeta {
// override type BASE <: MetaBase // You haven't defined type MetaBase yet
}
The type projection Meta#BASE
tells the compiler which BASE
you mean.
Unfortunately, a self reference doesn't work,
trait EnumMeta extends Meta with Iterable[EnumMeta#BASE] { ...
The compiler complains,
[error] illegal cyclic reference involving trait EnumMeta
[error] trait EnumMeta extends Meta with Iterable[EnumMeta#BASE] {
[error] ^
This limitation is unfortunate, and is a advantage of type parameters over type members. See Didierd's answer for reference.
(There's a second problem, type MetaBase
is undefined, but that's unrelated to your question.)
精彩评论