模块和记录字段

时间:2011-07-20 09:02:45

标签: module ocaml field record functor

我偶然发现了一个相当简单的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模块外部访问上面定义的yPoint2D字段,例如location.xlocation.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 *)

3 个答案:

答案 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.tMade2.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不同,您仍会在MakeA2Made1中获得相同的抽象类型和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 *)