Scala协方差和下限类型解释

Bla*_*son 12 generics scala type-bounds

我试图通过使用下限创建新的不可变类型的方法来了解协方差

class ImmutableArray[+T](item: T, existing: List[T] = Nil) {  
  private val items = item :: existing

  def append[S >: T](value: S) = new ImmutableArray[S](value, items)
}
Run Code Online (Sandbox Code Playgroud)

我知道type参数T不能在append方法中使用,因为它违反了规则但是如果我说一个Customer类和子类Student我仍然可以创建类型U Student.

我可以看到这有效,但为什么这不违反规则?我可以理解,如果我有一个Students 列表然后添加了一个Customer我只能返回一个Customers 列表,因为它不允许Customer分配给a,Student因为它是父类型.但为什么我可以使用Student

我错过了什么?

谢谢布莱尔

Gle*_*est 13

你的班级提供2个涉及T的操作:

  1. 施工

    nextImmutableArray = new ImmutableArray(nextT, priorImmutableArray)
    
    Run Code Online (Sandbox Code Playgroud)

    由于此操作,类型参数T必须是共变量:+ T.这允许您使用参数设置构造为类型的对象(T或T的子类型).

    想一想:通过加入瓦伦西亚橙来构建一系列橘子是有效的.

  2. 组合

    nextImmutableArray.append(newItemTorAncestor)
    
    Run Code Online (Sandbox Code Playgroud)

    此方法不会附加到您的数据结构.它有两个独立的元素(你的数组实例和一个额外的对象),并结合新构造的阵列中的他们.您可以考虑将方法名称更改为appendIntoCopy.更好的是,您可以使用名称+.但要最正确并与Scala约定保持一致,最好的名称是:+.

    当你问一个特定的问题时,为什么我会对一个'随机'方法名称感到困惑?

    因为该方法的精确性质决定了返回的数据结构是否是(a)非变体与T(b)共变体与T(c)反变体与T.

    • 开头:ImmutableArray [T] - 包含类型T(或子类型)
    • 结合:S型对象
    • 结果:ImmutableArray [S]
    • 如果允许S是T的正确子类型(超出T本身),则新数组不能包含T类型的原始元素!
    • 如果S是T类型或T的超类型,那么一切都很好 - 可以包含原始元素,加上新元素!

    当你结合数组和元素,新创建的数据结构必须有一个类型参数是共同的祖先类型的超类型.否则它不能包含原始元素.通常,当执行"a:+ b"时,其中A是数组[A],b是类型B,结果数据结构是数组[Some_SuperType_Of_Both_A_and_B].

    想想:如果我从一系列橘子开始,然后添加柠檬,我最终会得到一系列柑橘类水果(不是橙子,脐橙,也不是柠檬).


方法规则(严格输入,适应输出):

  • a)输入参数提供插入元素(变异):Co-Variant
  • a)输出参数从数据结构返回一个元素:Contra-Variant
  • c)输出参数,组合后返回数据结构:Contra-Variant
  • c)使用类型作为下限:"翻转"方差 ("Contra-variant to T"="Co-Variant to S,其具有下限T")

在附加的情况下:以T开头,输出数据结构=对比变量为T,类型S使用T作为下限,因此输入参数=与S的共变量.这意味着如果T1是T2的子类型则ImmutableArray [T1]是ImmutableArray [T2]的子类型,它可以在预期后者的任何地方被替换,所有方法都遵循Liskov的替换原则.


Jat*_*tin 10

第一个问题:

我知道类型参数T不能在append方法中使用,因为它违反了规则

好吧,它可以使用.S >: T简单地说,如果你传入一个S等于T或等于它的类型,那么S将被使用.如果你传递一个sublevel类型T然后T将被使用.

scala> class Animal
defined class Animal

scala> class Canine extends Animal
defined class Canine

scala> class Dog extends Canine
defined class Dog

scala> new ImmutableArray[Canine](new Canine)
res6: ImmutableArray[Canine] = ImmutableArray@a47775

scala> res6.append(new Animal)
res7: ImmutableArray[Animal] = ImmutableArray@1ba06f1

scala> res6.append(new Canine)
res8: ImmutableArray[Canine] = ImmutableArray@17e4626

scala> res6.append(new Dog)
res9: ImmutableArray[Canine] = ImmutableArray@a732f0
Run Code Online (Sandbox Code Playgroud)

上面的做法res6.append(new Dog)仍然给你类型犬的ImmutableArray.如果你想某种方式它是完全合理的,因为添加Dog to Canine Array仍将保留阵列Canine.但是将动物添加到犬阵列会使它成为动物,因为它不再是完美的犬(可以是磨牙或其他东西).

这是一个很好的例子,说明为什么通常知道反变体类型声明使其适合于写入(您的情况)和读取的协方差.

在你的榜样,我觉得混乱可能是因为你比较S >: TS super T(从Java世界).随着S super T你必然会有参数类型是超一流的T,它不会让你传递一个参数,即子类型T.在scala中,编译器会处理这个问题(感谢类型推断).