Где противоположность? - PullRequest
2 голосов
/ 24 мая 2011

Канонический пример исправления другого ковариантного класса выглядит следующим образом:

abstract class Stack[+A] {
  def push[B >: A]( x: B ) : Stack[B]
  def top: A
  def pop: Stack[A]

Теперь, если я удаляю неявную ковариацию и вручную аннотирую класс, я получаю следующее:

abstract class Stack[A] {
  def push[B >: A]( x: B ) : Stack[B]
  def top [B >: A]: B
  def pop [B >: A]: Stack[B]
  def cast[B >: A]: Stack[B]
}

(Быстрое доказательство корректности: Stack[A] имеет элементы типа A, поэтому, если B является более допустимым, мы всегда можем вернуть A вместо B. Аналогично, для любого стека A, мы можем использовать его вместо стека B, если B может принять A.)

Но теперь я немного запутался: где-то здесь должна быть противоположность, но все подтипыотношения здесь кажутся одинаковыми.Что случилось?

Чтобы уточнить, мы определяем контравариантный функтор F такой, что (a -> b) -> (F b -> F a).В частности, функтор F a на a -> r является контравариантным, как и (a -> b) -> ((b -> r) -> (a -> r)) просто путем составления функций.С точки зрения формализма, я ожидаю, что стрелки будут переворачиваться.Таким образом, с чисто синтаксической точки зрения, я запутываюсь, когда стрелки не переворачиваются (но так должно быть!). Мой аннотированный способ написания Scala - просто «естественное» представление контравариантности функций, такое, что вы даже не замечаетеЭто?Мой абстрактный класс не так?Что-то вводит в заблуждение во второй презентации?

1 Ответ

2 голосов
/ 24 мая 2011

Вы смотрите на те же отношения.Давайте подумаем о том, что означает Stack[+A]: если C является подклассом A, то Stack[C] рассматривается как подкласс Stack[A], т. Е. Он может заполнять класс A где угодно;со всеми методами, аннотированными с помощью обобщенных обобщенных наборов, это, конечно, верно, как вы указали.

Но вы не спроектировали свой исходный класс так, чтобы аргумент push находился в противоположной позиции.Эти отношения естественно возникают, когда вы накладываете ограничения на то, что вы можете обрабатывать - тогда, если подкласс означает, что метод может обрабатывать меньше, C[Subclass] действует как суперкласс C[Original], так как C[Original] может обрабатывать все, что может подклассобрабатывать (и многое другое).Но push может обрабатывать что угодно так, как вы его определили.

Так что именно так взаимодействуют границы типов и дисперсия: если вы разрешаете расширение типов именно в тех точках, которые находятся впротиворечивая позиция (то есть которая в противном случае ограничит вас), тогда вам разрешено быть ковариантным.В противном случае вы должны быть инвариантными или контравариантными.(Pop не позволяет вам быть контравариантным, поэтому вы должны быть инвариантными. См., Например, изменяемые коллекции, где инвариантность является нормой именно по этой причине - вы не вправе расширять тип натужиться.)

...