我偶然发现了一个相当简单的OCaml问题,但我似乎无法找到一个优雅的解决方案。我正在使用应用于相对简单模块的函子(它们通常在该类型上定义类型和一些函数),并通过添加更复杂的函数,类型和模块来扩展这些简单模块。简化版将是:
module type SIMPLE = sig
type t
val to_string : t -> string
val of_string : string -> t
end
module Complex = functor (S:SIMPLE) -> struct
include S
let write db id t = db # write id (S.to_string t)
let read db id = db # read id |> BatOption.map S.of_string
end
没有必要给简单模块命名,因为它的所有功能都存在于扩展模块中,而简单模块中的函数是由camlp4根据类型生成的。这些仿函数的习惯用法是:
module Int = Complex(struct
type t = int
end)
当我处理记录时出现问题:
module Point2D = Complex(struct
type t = { x : int ; y : int }
end)
let (Some location) = Point2D.read db "location"
似乎没有简单的方法可以从x
模块外部访问上面定义的y
和Point2D
字段,例如location.x
或location.Point2D.x
。我怎样才能做到这一点?
编辑:根据要求,这是一个显示问题的完整最小示例:
module type TYPE = sig
type t
val default : t
end
module Make = functor(Arg : TYPE) -> struct
include Arg
let get = function None -> default | Some x -> (x : t)
end
module Made = Make(struct
type t = {a : int}
let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
end)
let _ = (Made.get None).a (* <-- ERROR *)
答案 0 :(得分:4)
让我们看一下所涉及的一些模块的签名。这些是Ocaml生成的签名,它们是主要签名,即它们是理论允许的最常见签名。
module Make : functor (Arg : TYPE) -> sig
type t = Arg.t
val default : t
val get : t option -> t
end
module Made : sig
type t
val default : t
val get : t option -> t
end
注意方程Make(A).t = A.t
是如何保留的(因此Make(A).t
是透明类型的缩写),但Made.t
是抽象的。这是因为Made
是将仿函数应用于匿名结构的结果,因此在这种情况下参数类型没有规范名称。
记录类型是生成性的。在基础类型理论的层面上,所有生成类型的行为都像抽象类型,对于构造函数和析构函数具有一些语法糖。指定生成类型的唯一方法是给出它的名称,无论是原始名称还是通过一系列类型方程式扩展为原始名称的名称。
如果您复制Made
:
module Made1 = Make(struct
type t = {a : int}
let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
end)
module Made2 = Make(struct
type t = {a : int}
let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
end)
您可以获得两种不同的类型Made1.t
和Made2.t
,即使定义的右侧相同也是如此。这就是创生力的全部意义。
由于Made.t
是抽象的,因此它不是记录类型。它没有任何构造函数。由于缺少名称,构造函数在结构参数关闭时丢失了。
事实上,有了记录,人们通常需要语法糖,而不是生成。但Ocaml没有任何结构记录类型。它具有生成记录类型,并且它具有对象,从类型理论视图包含对象,但在实践中可以使用更多的工作并且具有较小的性能损失。
module Made_object = Make(struct
type t = <a : int>
let default = object method a = 0 end
end)
或者,如果要保持相同的类型定义,则需要为类型及其构造函数提供名称,这意味着命名结构。
module A = struct
type t = {a : int}
let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
end
module MadeA = Make(A)
请注意,如果您构建Make(A)
两次,则会获得相同的类型。
module MadeA1 = Make(A)
module MadeA2 = Make(A)
(好吧,这在这里并不值得注意,但与[{1}}和MadeA1
不同,您仍会在MakeA2
和Made1
中获得相同的抽象类型和Made2
上面的案例。那是因为现在有这些类型的名称:MadeA1.t = Make(A).t
。)
答案 1 :(得分:3)
首先,在您的上一个代码示例中,最后一行,您可能意味着.a
而不是.x
。
代码的问题在于,通过定义Make
仿函数的方式,t
中的Made
类型是抽象的:实际上,仿函数使用TYPE
将{a : int}
密封为抽象类型的签名。
以下设计可以避免这个问题,但是,它的设计却不同。
module type TYPE = sig
type t
val default : t
end
module Extend = functor(Arg : TYPE) -> struct
open Arg
let get = function None -> default | Some x -> (x : t)
end
module T = struct
type t = {a : int}
let default = { a = 0 }
end
module Made = struct
include T
include Extend(T)
end
let _ = Made.((get None).a)
答案 2 :(得分:1)
问题在于OCaml没有名称来引用t
类型的限定组件(在这种情况下是记录,但正常变体会出现同样的问题){{1 }}。命名未命名的解决问题:
Made
您还可以在functorial应用程序之外明确声明类型:
module F = struct
type t = {a : int}
let default = { a = 0 }
end
module Made = Make(F)
let _ = (Made.get None).F.a (* <-- WORKS *)