如何解决当记录类型具有相同标签时修复类型推断
考虑以下记录类型:
type t1 = {
label1: string option
; label2: (string * int) list
; label3: (string * int) list
; }
type t2 = {
label1: string option
; label2: (string * int) list
; label3: ((string * int) list) list
; }
我知道我不能在同一个模块中定义类型 t1 和 t2,因为 label1 和 label2具有相同的类型并在 t1 和 t2 中定义,这将破坏类型推断。因此,如果更改标签是不可能的,我有两个选择:
我可以使用模块命名空间:
module M1 = struct
type t1 = {
label1: string option
; label2: (string * int) list
; label3: (string * int) list
; }
end
module M2 = struct
type t2 = {
label1: string option
; label2: (string * int) list
; label3: ((string * int) list) list
; }
end
或者我可以在同一个文件中定义的每个函数中指定类型:
let f1 (param: t1) = ...
let f2 (param: t2) = ...
let f3 (param: t1) = ...
每种方法的优缺点是什么?
解决方法
首先,它不会破坏类型推断,您可以在同一模块中定义具有相同字段的多个记录以及具有相同构造函数的多个变体。一般不推荐使用,但非常好且定义明确。
当您定义两个共享某些字段名称的记录时,字段的最后一个定义会隐藏以前的定义,例如,
type t1 = {x : int}
type t2 = {x : char}
let x {x} = x
x
函数的类型为 t2 -> char
。这是因为类型推断是语法驱动的,并且从字段的名称到其记录的类型。在 OCaml 的现代版本中,您可以使用类型导向的构造函数消歧来访问阴影字段,例如,
let x : t1 -> _ = fun {x} -> x
由于我们限制了类型,编译器会在指定的记录中查找字段。
这是一个相当新的功能,在此之前,字段名称消歧只有两个选项。事实上,两者仍然是选项,可能是推荐的。
第一个选项是将每条记录放在自己的模块中。这是我个人的喜好。事实上,我根本不喜欢在我的接口中包含记录或变体,并且总是将它们打包在模块中并将它们作为实现细节隐藏在接口下。
module T1 = struct
type t = {x : int}
end
module T2 = struct
type t = {x : char}
end
let x_of_t1 {T1.x} = x
let x_of_t2 {T2.x} = x
但最好在每个模块中都提供val x : t -> _
函数并使用模块接口而不依赖于内部表示。
下一个也是最后一个选项是使用旧的和丑陋的前缀来消除名称的歧义。不是真正的解决方案(因为名称现在不同)但它很常见并且并不像看起来那么糟糕,例如,
type t1 = {t1_x : int}
type t2 = {t2_x : char}
我个人不喜欢这种解决方案,但它在各种程序分析项目中很常见,在这些项目中,您有许多共享相同结构但具有不同负载的中间语言。例如,来自解析树的常量 Pconst of string * loc
,与抽象语法树 (AST) 中的常量 Aconst of int * loc
与类型化 AST 中的常量 Tconst of int * typ * loc
,等等在。在这里使用前缀是实用的,参见。 T.Const
vs Tconst
,一个角色的经济! :) 和完善的传统。虽然我个人仍然更喜欢模块而不是所有其他选项。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。