Scala Abstract type members - inheritance and type bounds

落爺英雄遲暮 提交于 2020-01-01 12:25:10

问题


I ran into some strange situation in Scala today while I tried to refine the type bounds on an abstract type member.

I have two traits that define bounds on a type member and combine them in a concrete class. That works fine but when matching / casting with the trait combination only one of the two TypeBounds is "active" and I struggle to understand why ...

I tried to prepare an example:

trait L
trait R

trait Left {
  type T <: L
  def get: T
}

trait Right {
  type T <: R
}

now if I combine these two traits in one concrete class

val concrete = new Left with Right {
  override type T = L with R
  override def get: T = new L with R {}
}

I can access my member via get as intended

// works fine
val x1: L with R = concrete.get

but if I cast to (Left with Right) or pattern match I cannot access the member anymore. Dependent on the order I get either the type bounds from Left or from Right but not the combination of both.

// can only access as R, L with R won't work
val x2: R = concrete.asInstanceOf[Left with Right].get

// can only access as L, L with R won' work
val x3: L = concrete.asInstanceOf[Right with Left].get

I understand that Left with Right is not the same thing as Right with Left but in both cases both type bounds are included, so why can I only get one to work?

can anyone shed some light on why this is happening?


回答1:


the second type member overrides the first one.

trait L
trait R

trait Left {
  type T <: L
  def get: T
}

trait Right {
  type T <: R
}

object X {
  type LR = Left with Right // Right#T overrides Left#T, LR#T is now <: R
  type RL = Right with Left // Left#T overrides Right#T, LR#T is now <: L

  val concrete = new Left with Right {
    override type T = L with R
    override def get: T = new L with R {}
  }

  // ok
  val r: R = concrete.asInstanceOf[LR].get
  val l: L = concrete.asInstanceOf[RL].get

  // ok
  implicitly[LR#T <:< R]
  implicitly[RL#T <:< L]

  // doesn't compile, LR#T is a subclass of R because Right#T overrides Left#T
  implicitly[LR#T <:< L]
  // doesn't compile, RL#T is a subclass of L because Left#T overrides Right#T
  implicitly[RL#T <:< R]
}

In "concrete" you override the type member with L with R, but when you cast it to Left with Right you lose that refinement, and T becomes _ <: L or _ <: R depending on the order of the traits.

Since type members can be overridden, if you upcast (e.g. to LR or RL) you lose the refinement you applied in concrete. You could say concrete is at the same time a RL and a LR, but when you upcast it to LR or RL you lose the information you have in the other one




回答2:


Additionally to TrustNoOne's answer I can suggest following workaround with some limitations. You can design your own type combinator instead of with to overcome type overriding.

  trait L
  trait R
  trait Base {
    type T
    def get: T
  }
  trait Nil extends Base{
    type T = Any
  }
  trait ~+~[X[_ <: Base] <: Base, Y[_ <: Base] <: Base] extends Base {
    type T = Y[X[Nil]]#T
  }
  trait Left[B <: Base] extends Base {
    type T = B#T with L
  }
  trait Right[B <: Base] extends Base {
    type T = B#T with R
  }
  val concrete = new (Left ~+~ Right) {
    def get: T = new L with R {}
  }

  val x1: L with R = concrete.get
  val x2: R = concrete.asInstanceOf[Left ~+~ Right].get
  val x3: L = concrete.asInstanceOf[Right ~+~ Left].get

This code now compiles successfully, note however that we can not merge namespaces of combined types in new type, so all known methods should be defined in Base or derived in some typeclass via mechanic similar to shapeless HList



来源:https://stackoverflow.com/questions/35651064/scala-abstract-type-members-inheritance-and-type-bounds

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!