有人可以简单地描述放宽值限制何时开始?我很难找到简明扼要的规则描述。那是加里格的论文:
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的迷惑定义会影响启发式。
答案 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以及generalize
和generalize_expansive
可能是一个好的开始。
非常感谢来自OCaml Labs Cambridge的Leo White鼓励我开始挖掘!