Scala逆变和协变

3
我是一个有用的助手,可以为您翻译文本。
我正在尝试使用Scala的类型系统,并发现了一个奇怪的情况。我有充分的理由相信,我不理解协变和协方差。
这是我的问题案例:
我有两个类,Point和ColorPoint,其中ColorPoint是Point的子类。
class Point(val x : Int, val y : Int)
class ColorPoint(x : Int, y : Int, val red : Int, val green : Int, val blue : Int) extends Point(x,y) 

这个类将B类型转换为A类型,但是B应该是A的父类:
class CoVariance[+A]{
 def cast[B >: A](x : B) : A = {
  return x.asInstanceOf[A] 
 }
}

这个类将B转换为A,但是B应该是A的超类型:

class ContraVariance[-A]{
 def cast[B, A <: B](x : B) : A = {
    return x.asInstanceOf[A]
 }
}

案例1:

val co = new CoVariance[Point]
val color_point = new ColorPoint(1,2,3,4,5)
val point_co = co.cast(color_point) 
println(point_co.x)

如果我写出这个代码:
// Covariance[Point] -> 
// cast[B :> Point](x : B) : Point -> (fill in ColorPoint)
// Cast[ColorPoint :> Point] : Point 

我会期望这是不正确的,因为ColorPoint不是Point的超类型,但是scala并没有报错。
下一个:
val contra = new ContraVariance[Point]
val color_point_contra = new ColorPoint(1,2,3,4,5)
val point_contra = contra.cast(color_point_contra) 
println(point_contra.x)

如果我把这个写出来:
// ContraVariance[Point] -> 
// cast[B, Point <: B](x : B) : Point -> (fill in ColorPoint)
// cast[ColorPoint, Point <: ColorPoint] : Point 

我认为这是错误的,但scala并没有抱怨。我会说Point不是ColorPoint的子类型。
我的推理正确吗?还是我漏掉了什么?
2个回答

5
我认为您误解了协变和逆变位置的含义。它并不意味着您能在特定类型之间进行强制转换,而是建立参数化类型之间的继承关系。
您只能将类型参数标记为协变或逆变位置。当您使用 Container[+A] 时,意味着如果 AB 的子类型,则可以将所有 Container[A] 实例视为 Container[B] 的子类型。对于不可变容器类,这是有意义的: 您可以将 List[Person] 视为 List[Employee] 的父类。请注意,这并不涉及强制转换规则 - 这些规则保持不变。
逆变位置类似,但相反。如果您有 Writer[-A],则表示如果 BA 的子类型,则 Writer[A]Writer[B] 的子类型。您可以看到这也是有直观意义的: 如果您有一个 Writer[Person],它可以将 Person 写入某个目的地,如果您有一个只能写 Employees 的 writer Writer[Employee],那么让 Writer[Employee] 成为 Writer[Person] 的父类是有意义的,因为编写一个 Person 是编写完整的 Employee 的一个子任务,即使对于类型本身也是相反的。

啊,我现在明白了。这很像协变和逆变函数对象。是这样吗? - Edgar Klerks
我猜他们是可组合的;对于每个态射 f:A->Bg:B->C,我们有 Type(f):Type[A]->Type[B]Type(g):Type[B]->Type[C],那么似乎成立 Type(f.g)=Type(f).Type(g) - Edgar Klerks

2
  1. asInstanceOf[T] ignores typechecks. So you may even have the following cast:

    def cast[B](a:A):B = a.asInstanceOf[B]
    

    for any A and B.

    Thus in your case Scala won't complain.

  2. If I understand correctly, you want to have cast method only when the types are in proper relations (parent-child). I think, you needn't have +/- in class declaration. Only two different casts:

    implicit class CastToParent[A](a:A) {
      def cast[B >: A]:B = a.asInstanceOf[B]
    }
    
    
    implicit class CastToChild[A](a:A) {
      def cast[B <: A]:B = a.asInstanceOf[B]
    }
    

    this allows you to have the desired conversions.

    trait A
    trait B extends A
    trait C
    val a:A = new B {}
    val b   = a.cast[B] //parent to child
    val a1  = b.cast[A] //child to parent.
    val c   = a.cast[C] // don't compile
    

1
@EdgarKlerks asInstanceOf是个谎言。它的意思是“编译器,忘掉你所知道的,相信我说的话”。 - Daniel C. Sobral

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接