【问题标题】:Is there any alternatives to using covariant type in contravariant position?在逆变位置使用协变类型是否有任何替代方法?
【发布时间】:2015-04-11 09:40:09
【问题描述】:

我正在寻找一种从现有实例创建新实例的模式,以便我可以通过遍历其上方的层次结构来计算 Tile 的 UltimateBase。我尝试了以下方法,但在 buildTile 方法的参数 base 上得到“协变类型 A 出现在逆变位置”。是否有替代模式来构建依赖于先前实例的实例?

trait Color
trait Blue extends Color
trait Green extends Color

trait Tile[+A] {
  def declaredBase: Option[(Double, Tile[A])]

  final val ultimateBase: Option[(Double, Tile[A])] = ??? // Some implementation, not important

  // Can't be protected[this]
  def buildTile(name: String, multiple: Double, base: Tile[A]): Tile[A]
}

case class BlueTile(name: String, declaredBase: Option[(Double, Tile[Blue])]) extends Tile[Blue] {
  override def buildTile(name: String, multiple: Double, base: Tile[Blue]): Tile[Blue] = {
    // Something more complicated here
    this
  }
}

谢谢。

【问题讨论】:

    标签: scala generics covariance contravariance


    【解决方案1】:

    1) 可以单独声明逆变位置的类型,并在子类中实现:

    trait Tile[+A] {
      type T <: A
      def declaredBase: Option[(Double, Tile[A])]
    
      final val ultimateBase: Option[(Double, Tile[A])] = None // Some implementation, not important
    
      // Can't be protected[this]
      def buildTile(name: String, multiple: Double, base: Tile[T]): Tile[A]
    }
    
    case class BlueTile(name: String, declaredBase: Option[(Double, Tile[Blue])]) extends Tile[Blue] {
      type T = Blue
      override def buildTile(name: String, multiple: Double, base: Tile[T]): Tile[Blue] =
      {
        println(base.declaredBase)// Something more complicated here
        this
      }
    }
    
    scala> val x = new BlueTile("", None)
    x: BlueTile = BlueTile(,None)
    
    scala> val a: Tile[Color] = new BlueTile("", None).buildTile("", 1.0, x) //covariance works
    None
    a: Tile[Color] = BlueTile(,None)
    

    它在这种特殊情况下有效,因为您最终确实可以指定类型 T。

    2) 另一种方法是隐式添加def buildTile

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    
    trait Tile[+A] {  
      def declaredBase: Option[(Double, Tile[A])]
      final val ultimateBase: Option[(Double, Tile[A])] = None // Some implementation, not important 
    }
    
    case class BlueTile(name: String, declaredBase: Option[(Double, Tile[Blue])]) extends Tile[Blue]
    
    implicit class BuildFromBlue(t: Tile[Blue]) {
      def buildTile(name: String, multiple: Double, base: Tile[Blue]): Tile[Blue] = {
        println(base.declaredBase)// Something more complicated here
        t
      }
    }
    
    // Exiting paste mode, now interpreting.
    
    defined trait Tile
    defined class BlueTile
    defined class BuildFromBlue
    
    scala> val x = new BlueTile("", None)
    x: BlueTile = BlueTile(,None)
    
    scala> val a: Tile[Color] = new BlueTile("", None).buildTile("", 1.0, x) //covariance works
    None
    a: Tile[Color] = BlueTile(,None)
    

    3A) 最后一种选择是通过存在类型的协变。您可以将Tile[A] 声明为不变量,但在需要时需要Tile[_ &lt;: A]

    trait Tile[A] {
      def declaredBase: Option[(Double, Tile[A])]
      def buildTile(name: String, multiple: Double, base: Tile[A]): Tile[A]
    }
    
    case class BlueTile(name: String, declaredBase: Option[(Double, Tile[Blue])]) extends Tile[Blue] {
      override def buildTile(name: String, multiple: Double, base: Tile[Blue]): Tile[Blue] = this
    }
    
    scala> val x = new BlueTile("", None)
    x: BlueTile = BlueTile(,None)
    
    scala> val a: Tile[_ <: Color] = new BlueTile("", None).buildTile("", 1.0, x)
    a: Tile[_ <: Color] = BlueTile(,None)
    

    1,2,3A) 但是你不能在转换为更大的类型Tile[Color]后在逆变位置使用它:

    scala> a.buildTile("", 1.0, a)
    <console>:18: error: type mismatch;
    found   : Tile[_$1(in value res20)] where type _$1(in value res20) <: Color
    required: Tile[_$1(in value a)]
              a.buildTile("", 1.0, a)
                                   ^
    

    3B) 您可以将base 与逆变存在类型绑定来实现:

    trait Tile[+A] {
      def declaredBase: Option[(Double, Tile[A])]
      def buildTile(name: String, multiple: Double, base: Tile[_ >: A]): Tile[A]
    }
    
    case class BlueTile(name: String, declaredBase: Option[(Double, Tile[Blue])]) extends Tile[Blue] {
      override def buildTile(name: String, multiple: Double, base: Tile[_ >: Blue]): Tile[Blue] = {println(base.declaredBase); this}
    }
    
    scala> val x = new BlueTile("", None)
    x: BlueTile = BlueTile(,None)
    
    scala> val a: Tile[Color] = new BlueTile("", None).buildTile("", 1.0, x: Tile[Blue])
    None
    a: Tile[Color] = BlueTile(,None)
    
    scala> a.buildTile("", 1.0, a) //you can do it now
    None
    res23: Tile[Color] = BlueTile(,None)
    

    它的工作原理是 [_ &gt;: A] 实际上表示您不需要 liskov-substitution 来代替 base 本身。它将允许从Tile[Color] 使用buildTile。但是这种方法会将base 绑定到Tile[_ &gt;: Color] 以满足Tile[+A] 本身的liskov-substitution:

    scala> a.buildTile("", 1.0, a: Tile[Any]) //Tile[Any] also works
    None
    res27: Tile[Color] = BlueTile(,None)
    

    所以我建议将 A 限制为 Color(或者可能在子特征 od Tile 的某个地方):

    trait Tile[+A <: Color]{...}
    ...
    
    scala> a.buildTile("", 1.0, a) 
    None
    res33: Tile[Color] = BlueTile(,None)
    
    scala>  a.buildTile("", 1.0, a: Tile[Any])
    <console>:18: error: type arguments [Any] do not conform to trait Tile's type parameter bounds [+A <: Color]
               a.buildTile("", 1.0, a: Tile[Any])
                                       ^
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-16
      • 1970-01-01
      • 2013-11-29
      • 2019-03-12
      相关资源
      最近更新 更多