Дисперсия 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 является инвариантным.