问题描述
背景
我是Reason的相对较新来的人,并且感到惊讶的是,比较带有参数的变体有多么容易:
type t = Header | Int(int) | String(string) | Ints(list(int)) | Strings(list(string)) | Footer;
比较不同的变体很好并且可以预测:
/* not equal */
Header == Footer
Int(1) == Footer
Int(1) == Int(2)
/* equal */
Int(1) == Int(1)
这甚至适用于复杂类型:
/* equal */
Strings(["Hello","World"]) == Strings(["Hello","World"])
/* not equal */
Strings(["Hello","World"]) == Strings(["a","b"])
问题
是否可以通过我无法找到的现有内置操作符/函数或其他某种语言构造来仅比较类型构造器?
let a = String("a");
let b = String("b");
/* not equal */
a == b
/* for sake of argument,I want to consider all `String(_)` equal,but how? */
解决方法
可以通过检查值的内部表示形式来实现,但是我不建议这样做,因为它非常脆弱,而且我不确定跨这些内部版本的编译器版本和各种后端会做出什么保证。相反,我建议您要么编写手工构建的函数,要么使用一些ppx生成与您手工编写的代码相同的代码。
但这没什么好玩的,因此,所有这些,应该使用几乎没有记载的Obj
module:
let equal_tag = (a: 'a,b: 'a) => {
let a = Obj.repr(a);
let b = Obj.repr(b);
switch (Obj.is_block(a),Obj.is_block(b)) {
| (true,true) => Obj.tag(a) == Obj.tag(b)
| (false,false) => a == b
| _ => false
};
};
其中
equal_tag(Header,Footer) == false;
equal_tag(Header,Int(1)) == false;
equal_tag(String("a"),String("b")) == true;
equal_tag(Int(0),Int(0)) == true;
要了解此功能的工作原理,您需要了解OCaml如何在内部表示值。这在OCaml手册的Representation of OCaml data types章节中的Interfacing C with OCaml一节中进行了描述(例如,尽管在我看来,这已经表明有些迹象表明它可能不适用于各种JavaScript后端)至少现在是这样。我已经用BuckleScript / rescript测试过了,js_of_ocaml趋向于更接近内部。)
具体来说,本节对变量的表示方式进行了以下说明:
type t =
| A (* First constant constructor -> integer "Val_int(0)" *)
| B of string (* First non-constant constructor -> block with tag 0 *)
| C (* Second constant constructor -> integer "Val_int(1)" *)
| D of bool (* Second non-constant constructor -> block with tag 1 *)
| E of t * t (* Third non-constant constructor -> block with tag 2 *)
也就是说,没有有效载荷的构造函数直接表示为整数,而具有有效载荷的构造函数表示为带有标签的“块”。还要注意,块标记和非块标记是独立的,因此我们不能首先从比较的值中提取一些“通用”标记值。相反,我们必须检查它们是否都是块,然后然后比较它们的标签。
最后,请注意,尽管此函数将接受任何类型的值,但在编写时仅考虑了变体。比较其他类型的值可能会产生意外的结果。这是不使用它的另一个很好的理由。