我有一组Ocaml类型来表示语法树。有类型的程序,类,方法,表达式等。例如,方法由这样的记录类型表示:
type method = { return:typeid; args:typeid list; body:expr }
它包括返回类型,每个参数的类型和正文定义。我想键入检查语法树,并生成一种看起来非常类似于旧树的新树,除了每个表达式都有一个与之关联的显式typeid(仅在类型检查后才知道)。
一种选择是声明一组并行类型:
type typed_expr = expr * typeid
type typed_method = { return:typeid; args:typeid list; body:typed_expr }
(* ... there are more types *)
typed_method是必需的,因为typed_expr是一个不同的类型。但我不想为未经检查的AST和已检查的AST维护两组几乎相同的类型。
另一种方法是定义表达式如下:
type expr = {...; typ:typeid option}
这使我能够对检查器的输入和输出使用相同的类型定义。不同之处在于我将大量检查移动到已检查语法树的使用者代码中。这里有一个合同,typ
字段在类型检查器输出中永远不会是None
,并且在类型检查器输入中始终为None
。
现在,每次我使用输入树时,访问typ
字段内部值的唯一方法是首先检查它是否为None(它不应该是)。由于额外的检查,这使得所有后来的消费者代码都很难看。
这些方法似乎都不令我满意。你会如何模仿这个?
答案 0 :(得分:4)
第一个比第二个更好:拥有两组看起来相似的数据类型可能很糟糕,但它是安全的:你必须在第二种方法中处理的不变量由类型解决。实际上,OCaml编译器实现采用这种方法:请参阅parsetree.mli
和typedtree.mli
。
在第一个和第二个之间,您可能希望定义typ
字段参数化的数据类型:
type 'typ expr = { ...; typ : 'typ }
然后你可以使用unit expr
表示无类型AST,typeid expr
表示输入AST。
我仍然更喜欢为无类型和类型化设置不同数据类型的第一种方法,因为通常情况下两个世界的AST可能有一些其他差异而不是类型。