在介绍extensible variant types之前,我参加了有关OCaml的课程,但我对它们并不了解。我有几个问题:
请注意,我的问题具体是关于可扩展变量类型的,与建议的与此问题相同的问题不同(该问题是在引入EVT之前提出的!)。
答案 0 :(得分:1)
可扩展变体与标准变体的区别在于 运行时行为。
尤其是,扩展构造函数是驻留在内部的运行时值 定义它们的模块。例如,在
type t = ..
module M = struct
type t +=A
end
open M
第二行定义新的扩展构造函数值A
并将其添加到
M
的现有扩展构造函数在运行时。
相反,经典变体在运行时并不真正存在。
注意到我可以使用,可以观察到这种差异 一个仅mli的经典变体编译单元:
(* classical.mli *)
type t = A
(* main.ml *)
let x = Classical.A
然后使用{p>编译main.ml
ocamlopt classic.mli main.ml
没有麻烦,因为Classical
模块中没有价值。
与可扩展的变体相反,这是不可能的。如果有
(* ext.mli *)
type t = ..
type t+=A
(* main.ml *)
let x = Ext.A
然后输入命令
ocamlopt ext.mli main.ml
失败
错误:必需的模块“ Ext”不可用
因为缺少扩展构造函数Ext.A
的运行时值。
您还可以同时查看扩展构造函数的名称和ID
使用Obj
模块查看这些值
let a = [%extension_constructor A]
Obj.extension_name a;;
- :字符串=“ M.A”
Obj.extension_id a;;
- :int = 144
(此id
十分脆弱,其价值也不是特别有意义。)
重要的一点是,扩展构造函数使用它们的区别
内存位置。因此,实现了带有n
参数的构造函数
作为带有n+1
参数的块,其中第一个隐藏参数是扩展名
构造函数:
type t += B of int
let x = B 0;;
在这里,x
包含两个字段,而不是一个字段:
Obj.size (Obj.repr x);;
- :int = 2
第一个字段是扩展构造函数B
:
Obj.field (Obj.repr x) 0 == Obj.repr [%extension_constructor B];;
- :bool = true
上一条语句也适用于n=0
:可扩展的变体永远不会
表示为带标记的整数,与经典变体相反。
由于编组不能保持物理上的平等,因此意味着可扩展 总和类型不能在不丢失其身份的情况下进行编组。例如,
let round_trip (x:'a):'a = Marshall.from_string (Marshall.to_string x []) 0
然后使用
测试结果 type t += C
let is_c = function
| C -> true
| _ -> false
导致失败:
is_c (round_trip C)
- :bool = false
因为在读取编组值时往返分配了一个新块 这与异常已经存在的问题相同,因为异常 是可扩展的变体。
这也意味着可扩展类型的模式匹配有很大不同 在运行时。例如,如果我定义一个简单的变体
type s = A of int | B of int
并将函数f
定义为
let f = function
| A n | B n -> n
编译器足够聪明,可以优化此功能,从而只需访问 参数的第一个字段。
您可以使用ocamlc -dlambda
检查上面的函数是否表示为
Lambda中介表示为:
(function param/1008 (field 0 param/1008)))
但是,对于可扩展的变体,我们不仅需要默认模式
type e = ..
type e += A of n | B of n
let g = function
| A n | B n -> n
| _ -> 0
,但我们还需要将参数与 比赛导致比赛的Lambda IR更复杂
(function param/1009
(catch
(if (== (field 0 param/1009) A/1003) (exit 1 (field 1 param/1009))
(if (== (field 0 param/1009) B/1004) (exit 1 (field 1 param/1009))
0))
with (1 n/1007) n/1007)))
最后,以可扩展变体的实际示例作为结束, 在OCaml 4.08中,格式模块替换了其基于字符串的用户定义标签 具有可扩展的变体。
这意味着定义新标签看起来像这样:
首先,我们从新标签的实际定义开始
type t = Format.stag = ..
type Format.stag += Warning | Error
然后这些新标签的翻译功能是
let mark_open_stag tag =
match tag with
| Error -> "\x1b[31m" (* aka print the content of the tag in red *)
| Warning -> "\x1b[35m" (* ... in purple *)
| _ -> ""
let mark_close_stag _tag =
"\x1b[0m" (*reset *)
安装新标签的步骤如下
let enable ppf =
Format.pp_set_tags ppf true;
Format.pp_set_mark_tags ppf true;
Format.pp_set_formatter_stag_functions ppf
{ (Format.pp_get_formatter_stag_functions ppf ()) with
mark_open_stag; mark_close_stag }
通过一些帮助功能,可以使用这些新标签进行打印
Format.printf "This message is %a.@." error "important"
Format.printf "This one %a.@." warning "not so much"
与字符串标签相比,优点不多:
mark_open_stag
函数是安全的:
每个函数只能识别自己的扩展构造函数。