Дисперсия OCaml (+ 'a, -'a) и инвариантность
После написания этого куска кода
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {info : 'a list}
end
Я понял, что мне нужна info
чтобы быть изменчивым.
Я написал тогда:
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {mutable info : 'a list}
end
Но, удивительно,
Type declarations do not match:
type 'a t = { mutable info : 'a list; }
is not included in
type +'a t
Their variances do not agree.
О, я помню, что слышал о дисперсии. Это было что-то о ковариации и контравариантности. Я смелый человек, я найду о своей проблеме в одиночку!
Я нашел эти две интересные статьи (здесь и здесь) и понял!
я могу написать
module type TS = sig
type (-'a, +'b) t
end
module T : TS = struct
type ('a, 'b) t = 'a -> 'b
end
Но потом я удивился. Почему эти изменчивые типы данных являются инвариантными, а не просто ковариантными?
Я имею в виду, что я понимаю, что 'A list
может рассматриваться как подтип 'A list
('A | 'B) list
потому что мой список не может измениться. То же самое для функции, если у меня есть функция типа 'A | 'B → 'C
'A | 'B → 'C
его можно рассматривать как подтип функции типа 'A → 'C | 'D
'A → 'C | 'D
потому что, если моя функция может обрабатывать 'A
и 'B
она может обрабатывать только 'A
и если я только возвращаю 'C
я точно могу ожидать 'C
или 'D
(но я получу только 'C
').
Но для массива? Если у меня есть 'A array
я не могу рассматривать его как 'A array
('A | 'B) array
потому что, если я изменяю элемент в массиве, помещая 'B
тогда мой тип массива неправильный, потому что он действительно является ('A | 'B) array
а не 'A array
больше. Но как насчет ('A | 'B) array
как ('A | 'B) array
'A array
? Да, это было бы странно, потому что мой массив может содержать 'B
но странным образом я думал, что это то же самое, что и функция. Возможно, в конце концов, я не все понял, но я хотел изложить свои мысли здесь, потому что мне потребовалось много времени, чтобы понять это.
TL; DR:
постоянный: +'a
функции: -'a
изменяемый: инвариант ('a
)? Почему я не могу заставить его быть -'a
?
Ответы
Ответ 1
Я думаю, что самое легкое объяснение состоит в том, что изменяемое значение имеет две внутренние операции: getter и setter, которые выражаются с использованием полей доступа и синтаксиса набора полей:
type 'a t = {mutable data : 'a}
let x = {data = 42}
(* getter *)
x.data
(* setter *)
x.data <- 56
У Getter есть тип 'a t -> 'a
, где переменная типа 'a
возникает с правой стороны (поэтому она накладывает ограничение на ковариацию), а у установщика есть тип 'a t -> 'a -> unit
, где переменная типа встречается слева от стрелка, которая налагает контравариантное ограничение. Итак, у нас есть тип, ковариантный и контравариантный, это означает, что переменная типа 'a
является инвариантной.
Ответ 2
Ваш тип t
в основном позволяет выполнять две операции: получение и настройка. Неформально получение имеет тип 'a t -> 'a list
, а параметр имеет тип 'a t -> 'a list -> unit
. Комбинированный, 'a
встречается как в положительном, так и в отрицательном положении.
[РЕДАКТИРОВАТЬ: Ниже приведена (надеюсь) более ясная версия того, что я написал в первую очередь. Я считаю, что это лучше, поэтому я удалил предыдущую версию.]
Я попытаюсь сделать его более явным. Предположим, что sub
является собственным подтипом super
и witness
- это некоторое значение типа super
, которое не является значением типа sub
. Теперь пусть f : sub -> unit
- некоторая функция, которая терпит неудачу при значении witness
. Для обеспечения безопасности witness
никогда не передается f
. Я покажу на примере, что безопасность типа не выполняется, если разрешено либо обрабатывать sub t
как подтип super t
, либо наоборот.
let v_super = ({ info = [witness]; } : super t) in
let v_sub = ( v_super : sub t ) in (* Suppose this was allowed. *)
List.map f v_sub.info (* Equivalent to f witness. Woops. *)
Так что лечение super t
в качестве подтипа sub t
невозможно. Это показывает ковариацию, о которой вы уже знали. Теперь для контравариантности.
let v_sub = ({ info = []; } : sub t) in
let v_super = ( v_sub : super t ) in (* Suppose this was allowed. *)
v_super.info <- [witness];
(* As v_sub and v_super are the same thing,
we have v_sub.info=[witness] once more. *)
List.map f v_sub.info (* Woops again. *)
Таким образом, рассмотрение sub t
как подтипа super t
также не может быть разрешено, показывая контравариантность. Вместе, 'a t
является инвариантным.