OCaml方差(+'a,-a)和不变性

时间:2016-11-09 15:10:58

标签: ocaml covariance contravariance invariance

写完这段代码后

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.

哦,我记得听说过 variance 。它是关于协方差逆变的东西。我是一个勇敢的人,我会独自找到我的问题!

我找到了这两篇有趣的文章(herehere),我明白了!

我可以写

module type TS = sig
  type (-'a, +'b) t
end

module T : TS = struct
  type ('a, 'b) t = 'a -> 'b
end

但后来我想知道。为什么 mutable 数据类型是不变的而不仅仅是协变?

我的意思是,我了解'A list可以被视为('A | 'B) list的子类型,因为我的列表无法更改。对于函数来说,同样的事情,如果我有类型'A | 'B -> 'C的函数,它可以被视为类型为'A -> 'C | 'D的函数的子类型,因为如果我的函数可以处理'A和{{1它只能处理'B,如果我只返回'A,我可以肯定期待'C'C(但我会只得到'D。)

但对于阵列?如果我有'C我不能将它视为'A array,因为如果我修改数组中放置('A | 'B) array的元素,那么我的数组类型是错误的,因为它确实是一个'B而不是('A | 'B) array。但'A array('A | 'B) array相比如何呢?是的,这很奇怪,因为我的数组可以包含'A array但奇怪的是我认为它与函数相同。也许,最后,我并不理解所有事情,但我想在这里提出我的想法,因为我花了很长时间才理解它。

TL; DR

  

持久性:'B

     

功能:+'a

     

mutable:invariant(-'a)?为什么我不能强迫它'a

2 个答案:

答案 0 :(得分:16)

我认为最简单的解释是,一个可变值有两个内部操作:getter和setter,它们使用字段访问和字段集语法表示:

type 'a t = {mutable data : 'a}

let x = {data = 42}

(* getter *)
x.data

(* setter *)
x.data <- 56

Getter的类型为'a t -> 'a,其中'a类型变量出现在右侧(因此它强加了协方差约束),并且setter的类型为'a t -> 'a -> unit,其中类型变量出现在箭头的左侧,这会产生逆变约束。所以,我们有一个协变和逆变的类型,这意味着类型变量'a是不变的。

答案 1 :(得分:6)

您的类型t基本上允许两种操作:获取和设置。非正式地,获取类型'a t -> 'a list并且设置类型为'a t -> 'a list -> unit。合并后,'a同时出现在正面和负面位置。

[编辑:以下是我首先写的(希望)更清晰的版本。我认为它更优越,所以我删除了以前的版本。]

我会尽量让它更明确。假设subsuper的正确子类型,witnesssuper类型的某个值,它不是类型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一起是不变的。