在OCaml中,宽松的价值限制什么时候开始?

时间:2013-03-22 02:28:02

标签: ocaml

有人可以简单地描述放宽值限制何时开始?我很难找到简明扼要的规则描述。那是加里格的论文:

http://caml.inria.fr/pub/papers/garrigue-value_restriction-fiwflp04.pdf

但它有点密集。任何人都知道一个简单的来源?

附录

下面添加了一些很好的解释,但我无法找到有关以下行为的解释:

# let _x = 3 in (fun () -> ref None);;
- : unit -> 'a option ref = <fun>
# let _x = ref 3 in (fun () -> ref None);;
- : unit -> '_a option ref = <fun>

任何人都可以澄清以上内容吗?为什么封闭let的RHS中的ref的迷惑定义会影响启发式。

4 个答案:

答案 0 :(得分:9)

我不是一个类型理论家,但这是我对加里格的解释的解释。您有一个值V.从通常值限制下分配给V(在OCaml中)的类型开始。类型中将存在一些数字(可能为0)单态类型变量。对于仅出现在类型中的协变位置(在函数箭头的右侧)的每个此类变量,您可以将其替换为完全多态类型变量。

论点如下。由于您的单形变量是一个变量,您可以想象用任何单一类型替换它。所以你选择一个无人居住的类型U.现在因为它只处于协变位置,所以U可以被任何超类型替换。但是每种类型都是无人居住类型的超类型,因此用完全多态变量替换它是安全的。

因此,当你有(仅将出现的)单态变量出现在协变位置时,放宽的值限制就开始了。

(我希望我有这个权利。当然,正如octref建议的那样,@ gasche会做得更好。)

答案 1 :(得分:5)

Jeffrey提供了放松正确的原因的直观解释。至于何时有用,我认为我们可以首先重现有关链接的答案octref:

  

你可以放心地忽略那些细微之处,直到有一天你遇到一个你自己的抽象类型的问题,而不是你想要的多态,然后你应该记住,签名中的协方差注释可能会有所帮助。

     

几个月前我们讨论过on reddit/ocaml

     

请考虑以下代码示例:

module type S = sig
  type 'a collection
  val empty : unit -> 'a collection
end

module C : S = struct
  type 'a collection =
    | Nil
    | Cons of 'a * 'a collection
  let empty () = Nil
end

let test = C.empty ()
     

test获得的类型为'_a C.collection,而不是您期望的'a C.collection。它不是多态类型('_a是一个尚未完全确定的单态推理变量),在大多数情况下你不会满意。

     

这是因为C.empty ()不是一个值,所以它的类型不是一般化的(〜多态)。要从宽松的值限制中受益,您必须标记抽象类型'a collection协变:

module type S = sig
  type +'a collection
  val empty : unit -> 'a collection
end
     

当然这只会发生,因为模块C已使用签名S密封:module C : S = ...。如果模块C没有给出明确的签名,那么类型系统会推断出最一般的方差(这里是协方差),并且人们不会注意到这一点。

     

针对抽象接口进行编程通常很有用(当定义仿函数,或强制执行幻像类型规则或编写模块化程序时),因此这种情况肯定会发生,因此了解宽松值限制是有用的。 / p>

这是一个例子,当你需要了解它以获得更多的多态性时,因为你设置了一个抽象边界(一个带有抽象类型的模块签名)并且它不能自动工作,你明确地说抽象类型是协变的。

在大多数情况下,当您操作多态数据结构时,它会在没有您通知的情况下发生。由于放松,[] @ []只有多态类型'a list

具体但更高级的例子是Oleg的Ber-MetaOCaml,它使用类型('cl, 'ty) code来表示分段构建的引用表达式。 'ty表示引用代码的结果类型,'cl是一种幻像区域变量,可以保证在引用代码中变量的范围是正确的时,它仍然是多态的。由于这种情况依赖于多态性,在这种情况下,引用的表达式是通过组合其他引用的表达式来构建的(因此通常不是值),如果没有宽松的值限制,它基本上根本不起作用(这是他优秀但技术性方面的一个侧面评论{{3关于类型推断)。

答案 2 :(得分:1)

虽然我对这个理论不是很熟悉,但我已经问了一个问题 gasche为我提供了a concise explanation。该示例只是OCaml地图模块的一部分。看看吧!
也许他将能够为您提供更好的答案。 @gasche

答案 3 :(得分:1)

为什么附录中给出的两个例子的输入方式不同,这让我困惑了几天。以下是我通过深入研究OCaml编译器代码所发现的(免责声明:我既不是OCaml的专家,也不是ML类型系统的专家)。

小结

# let _x = 3 in (fun () -> ref None);;        (*  (1)  *)
- : unit -> 'a option ref = <fun>

给出了多态类型(想象∀ α. unit → α option ref)而

# let _x = ref 3 in (fun () -> ref None);;    (*  (2)  *)
- : unit -> '_a option ref = <fun>

被赋予单态类型(想象unit → α option ref,即类型变量α未被普遍量化)。

直觉

出于类型检查的目的,OCaml编译器看不到示例(2)和

之间的区别
# let r = ref None in (fun () -> r);;         (*  (3)  *)
- : unit -> '_a option ref = <fun>

因为它没有查看let的主体以查看是否实际使用了绑定变量(正如人们所预期的那样)。但是(3)显然必须被赋予单态类型,否则多态类型的参考单元可能会逃脱,可能导致内存损坏等不良行为。

为扩展

要理解为什么(1)和(2)按照它们的方式输入,让我们看看OCaml编译器如何实际检查let表达式是否为值(即&# 34;非扩张&#34;)与否(见is_nonexpansive):

let rec is_nonexpansive exp =
  match exp.exp_desc with
    (* ... *)
  | Texp_let(rec_flag, pat_exp_list, body) ->
      List.for_all (fun vb -> is_nonexpansive vb.vb_expr) pat_exp_list &&
      is_nonexpansive body
  | (* ... *)

所以let - 表达式是一个值,如果它的主体和所有绑定变量都是值。

在附录中给出的两个例子中,正文是fun () -> ref None,这是一个函数,因此是一个值。两段代码之间的区别在于3是值,ref 3不是。因此,OCaml认为第一个let值而不是第二个值。

输入

再看一下OCaml编译器的代码,我们可以看到表达式是否被认为是扩展的,决定了let - 表达式的类型是如何推广的(参见type_expression):

(* Typing of toplevel expressions *)

let type_expression env sexp =
  (* ... *)
  let exp = type_exp env sexp in
  (* ... *)
  if is_nonexpansive exp then generalize exp.exp_type
  else generalize_expansive env exp.exp_type;
  (* ... *)

由于let _x = 3 in (fun () -> ref None)是非扩张的,因此使用generalize键入它,这使其具有多态类型。另一方面,let _x = ref 3 in (fun () -> ref None)通过generalize_expansive输入,给它一个单形类型。

就我而言。如果你想深入挖掘,阅读Oleg Kiselyov的Efficient and Insightful Generalization以及generalizegeneralize_expansive可能是一个好的开始。

非常感谢来自OCaml Labs Cambridge的Leo White鼓励我开始挖掘!