【问题标题】:Scala Abstract type members - inheritance and type boundsScala 抽象类型成员 - 继承和类型界限
【发布时间】:2016-02-26 11:55:53
【问题描述】:

我今天在 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

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

// 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 不同,但在这两种情况下,两种类型的界限都包括在内,那么为什么我只能让一个工作?

谁能解释为什么会这样?

【问题讨论】:

    标签: scala abstract-type type-bounds type-members


    【解决方案1】:

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

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

    在“concrete”中,您使用L with R 覆盖类型成员,但是当您将其转换为Left with Right 时,您将失去该细化,并且 T 变为 _ <: l _ r>

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

    【讨论】:

    • 你可以阅读更多关于为什么现在 scala 覆盖类型以及如何在这个概念中为下一代 scala cs.uwm.edu/~boyland/fool2012/papers/fool2012_submission_3.pdf 做不同的事情
    • 我还没有时间深入研究 dotty,但我非常期待!
    • 你可以在这个视频youtube.com/watch?v=WxyyJyB_Ssc 中看到一些关于未来的精彩。关于无形包括的备注
    • 嘿,详细答案的坦克
    • 嘿,坦克为你们俩提供详细的答案!如果我理解正确:问题是 Left with Right 是一种将 T 绑定到 _ <: r a.>L with R b.) 与 L => 它不能被实例化(trait wrong extends Left with Right自 R != L 后无法编译)要转换为的正确类型是 asInstanceOf[Left with Right { type T = L with R},它也可以工作(-:
    【解决方案2】:

    除了 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 中定义或通过类似于无形HList 的机制派生在某个类型类中

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-05
      • 2016-05-08
      • 2021-01-23
      相关资源
      最近更新 更多