Question

I want to do the following, but "Iterable[BASE]" won't compile. What is 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]
Was it helpful?

Solution

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
}

OTHER TIPS

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.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top