Avoiding redundant generic parameters in Scala

こ雲淡風輕ζ 提交于 2019-12-05 04:09:17
Eduardo Pareja Tobes

Update given this answer I'm not sure whether this should be considered a bug or not

You've hit SI-4377; if you provide explicit type ascriptions you'll get an error which I'm guessing just exposes that type projections are implemented using existentials:

trait Ident { }

trait Container {
  type I <: Ident
  def foo(id: I): String
}

trait Entity {

  type C <: Container
  def container: C
  def foo(id: C#I): String = (container: C).foo(id: C#I)
  // you will get something like: type mismatch;
  // [error]  found   : Entity.this.C#I
  // [error]  required: _3.I where val _3: Entity.this.C
  // as I said above, see https://issues.scala-lang.org/browse/SI-4377
}

It is not an understatement to say that this (bug?) makes generic programming with type members a nightmare.

There is a hack though, which consists in casting values to a hand-crafted self-referential type alias:

case object Container {

  type is[C <: Container] = C with Container {

    type I = C#I
    // same for all other type members, if any
  }

  def is[C <: Container](c: C): is[C] = c.asInstanceOf[is[C]]
}

Now use it and Entity compiles:

trait Entity {

  type C <: Container
  def container: C
  def foo(id: C#I): String = Container.is(container).foo(id)
  // compiles!
}

This is of course dangerous, and as a rule of thumb it is safe only if C and all its type members are bound to a non-abstract type at the point it will be used; do note that this will not always be the case, as Scala lets you leave "undefined" type members:

case object funnyContainer extends Container {

  // I'm forced to implement `foo`, but *not* the `C` type member
  def foo(id: I): String = "hi scalac!"
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!