【问题标题】:Method inheritance on contravariant type逆变类型的方法继承
【发布时间】:2015-10-18 08:18:46
【问题描述】:

我已经定义了两个类型类:

trait WeakOrder[-X] { self =>
  def cmp(x: X, y: X): Int
  def max[Y <: X](x: Y, y: Y): Y = if (cmp(x, y) >= 0) x else y
  def min[Y <: X](x: Y, y: Y): Y = if (cmp(x, y) <= 0) x else y
}

trait Lattice[X] { self =>
  def sup(x: X, y: X): X
  def inf(x: X, y: X): X
}

我想做以下事情:

trait TotalOrder[-X] extends Lattice[X] with WeakOrder[X] { self =>
  def sup(x: X, y: X): X = max(x, y)
  def inf(x: X, y: X): X = min(x, y)
}

但这是不可能的,因为逆变类型 X 出现在协变位置(supinf 的返回值)。

但是,从语义上讲,这是正确的:maxmin 带有类型签名 max[Y &lt;: X](x: Y, y: Y): Y 编码了这样一个事实:max / min 的返回值必须是两个参数之一。

我尝试执行以下操作:

trait TotalOrder[-X] extends Lattice[X] with WeakOrder[X] { self =>
  def sup[Y <: X](x: Y, y: Y): Y = max(x, y)
  def inf[Y <: X](x: Y, y: Y): Y = min(x, y)
}

但是,方法def sup[Y &lt;: X](x: Y, y: Y): Y 不能继承def sup[X](x: X, y: X): X。编译器抱怨类型签名不匹配。但是前一个(带有现场差异注释)比后一个签名施加了更强的类型限制。为什么前者不能继承后者?如何绕过TotalOrder[-X] 的逆变类型限制(语义上,总订单是逆变的)?

【问题讨论】:

    标签: scala typeclass contravariance


    【解决方案1】:

    这在语义上是不正确的。从协变和逆变的定义应该就清楚了,但我还是举个例子吧:

    假设我们有实体的层次结构:

    class Shape(s:Float)
    class Circle(r:Float) extends Shape(Math.PI.toFloat * r * r)
    

    假设您尝试过创建逆变订单是可能的:

    trait CircleOrder extends TotalOrder[Circle] {
       // compare by r
    }
    
    trait ShapeOrder extends TotalOrder[Shape] {
      // compare by s
    }
    

    根据逆变器的定义,如Shape &lt;: CircleCircleOrder &lt;: ShapeOrder (CircleOrder 是 ShapeOrder 的超类型)

    假设我们有一个以 CircleOrder 作为参数的客户端 并用它来比较圆圈:

    def clientMethod(circleOrder:TotalOrder[Circle]) = {
      val maxCircle = circleOrder.max(???, ???) // expected to return Circle
      maxCircle.r // accessing field that is present only in circle
    }
    

    那么,根据继承的定义,应该可以通过 ShapeOrder 而不是 CircleOrder(记住,ShapeOrder 是子类型):

    clientMethod(new ShapeOrder {/*...*/})
    

    显然它不起作用,因为客户仍然希望订单返回圆形,而不是形状。

    我认为在您的情况下,最合理的方法是使用常规泛型。

    更新

    这是确保类型安全的方法,但有点难看。

        trait WeakOrder[-X] {
          def cmp(x: X, y: X): Int
          def max[T](x: X with T, y: X with T): T =
            if (cmp(x, y) >= 0) x else y
          def min[T](x: X with T, y: X with T): T =
            if (cmp(x, y) <= 0) x else y
        }
    
        trait Lattice[X] {
          def sup[T](x: X with T, y: X with T): T
          def inf[T](x: X with T, y: X with T): T
        }
    
        trait TotalOrder[-X] extends Lattice[X] with WeakOrder[X] {
          def sup[T](x: X with T, y: X with T): T = max(x, y)
          def inf[T](x: X with T, y: X with T): T = min(x, y)
        }
    

    【讨论】:

    • 我明白你在说什么,这就是我试图通过签名max[Y &lt;: X](x: Y, y: Y): Y 来做的事情。将两个圆圈传递给 ShapeOrdermax 函数应该返回 Circle 而不是 Shape 因为 Circle <: shape>max 的结果类型应该是Circle
    • @TongfeiChen 将类型设为逆变意味着你需要有可能做这样的事情:val cOrd:CircleOrderByR = new ShapeOrderByS。你确定这是你需要的吗?我认为不同的顺序应该是不相关的。
    • 全阶,根据其数学定义,是反对称、传递和全阶的阶。整体是指每两个元素都可以进行比较。一个总订单只依赖一个函数cmp(带有签名(X,X)=> Int)来定义,因此它自然是逆变的。例如,实数的全序(Double)自然意味着整数(Int)的全序。
    • @TongfeiChen 所以你是说,我们可以使用TotalOrder[Circle] 的实现是找到最多两个Shapes
    • Circle &lt;: Shape;因此TotalOrder[Shape] &lt;: TotalOrder[Circle](因此是逆变的)。这意味着形状上的顺序是圆形上的顺序;即 TotalOrder[Shape] 的实现可用于查找两个 Circles 的最大值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-30
    • 2016-11-19
    • 2014-02-28
    • 1970-01-01
    • 2011-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多