4

今天我在 Scala 中遇到了一些奇怪的情况,当时我试图改进抽象类型成员的类型边界。

我有两个特征定义类型成员的边界并将它们组合在一个具体的类中。这很好用,但是当与特征组合匹配/转换时,只有两个 TypeBounds 之一是“活动的”,我很难理解为什么......

我试图准备一个例子:

trait L
trait R

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

trait Right {
  type T <: R
}

现在,如果我将这两个特征结合在一个具体的类中

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

我可以按预期通过 get 访问我的会员

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

但是如果我转换为(左与右)或模式匹配,我将无法再访问该成员。根据顺序,我可以从左或右获得类型边界,但不是两者的组合。

// 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

我知道 Left with Right 与 Right with Left 不同,但在这两种情况下,两种类型的界限都包括在内,那么为什么我只能让一个工作呢?

谁能解释为什么会这样?

4

2 回答 2

9

第二种类型的成员覆盖第一种。

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]
}

在“具体”中,您使用 覆盖类型成员L with R,但是当您将其强制转换时,Left with Right您会失去该细化,并且 T 变为 _ <: L 或 _ <: R 取决于特征的顺序。

由于类型成员可以被覆盖,如果你向上转换(例如到 LR 或 RL),你将失去你在具体中应用的细化。你可以说混凝土同时是一个 RL 和一个 LR,但是当你将它向上转换为 LR 或 RL 时,你会丢失你在另一个中的信息

于 2016-02-26T12:09:29.773 回答
4

除了 TrustNoOne 的回答之外,我还可以建议遵循一些限制的解决方法。您可以设计自己的类型组合器,而不是with克服类型覆盖。

  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

此代码现在编译成功,但请注意,我们无法将组合类型的命名空间合并到新类型中,因此所有已知方法都应Base通过类似于 shapeless 的机制在某个类型类中定义或派生HList

于 2016-02-26T13:01:08.263 回答