Search code examples
genericsscalaabstract-data-type

How to mix Generics and Abstract Types in Scala?


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]

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
    }