Где контравариантность?

#scala #type-theory

#scala #теория типов

Вопрос:

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

стек абстрактного класса[ A] { 
 def push[B >: A]( x: B) : Стек[B]
 определение сверху: A 
 всплывающее окно определения: Стек[A]

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

стек абстрактного класса[A] { 
 def push[B >: A]( x: B) : Стек[B]
 def top [B >: A]: B 
 def pop [B>: A]: Стек[B]
 приведение def[B>: A]: Stack[B]
}

(Быстрое доказательство правильности: a 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. Когда мне говорят, что отношение контравариантно, я ожидаю, что будет отношение подтипа A <: B и соответствующее отношение подтипа F A >: F B : я ожидаю, что стрелки будут переворачиваться. Но я этого нигде здесь не вижу. Итак, какая конкретная часть кода, которую я записал, делает аргумент push контравариантным?

3. В scala аннотация отклонения «-» указывает, что тип является контравариантным. Ваш тип стека не имеет такого отношения и, в любом случае, Stack[Apple] логически не является супертипом Stack[Fruit] , даже если вы его добавили.

4. Стек, конечно, не контравариантен. Но аргумент push должен быть.

Ответ №1:

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

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

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

Комментарии:

1. То есть ты хочешь сказать, что я должен был написать: push[A >: C](X : C) : Stack[A]?

2. @Edward Z. Yang — Я говорю, что вы не можете писать push для коварианты Stack без B >: A , и если вы напишете это естественным образом, вы не увидите, как стрелки переключаются. Вам нужен случай, когда существует контраст между стрелками, которые переворачиваются, и теми, которые этого не делают.