模块和记录字段

标签 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"

似乎没有简单的方法可以从 Point2D 模块外部访问上面定义的 xy 字段,例如 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 *)

最佳答案

让我们看一下所涉及的一些模块的签名。这些是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)

(好吧,这在这里并不引人注目,但您仍然会在 MadeA1MakeA2 中获得相同的抽象类型,这与上面的 Made1Made2 情况。这是因为现在这些类型有了一个名称:MadeA1.t = Make(A).t。)

关于模块和记录字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6759419/

相关文章:

ocaml - 在父签名中进一步细化嵌套模块的签名

ocaml - Ocaml 中函数之间的循环依赖

javascript - 使用 jquery 自动输入表单

mysql - 如何从 MySQL 中的相同数据更新字段

java - 如何访问java中对象的通用数组列表中的项目字段?

module - 使用 Rcpp 模块的通用方法

python - 导入特定于版本的 python 模块的最佳方法

scala - "Brodal search trees"真的实现了实用吗?

python - NoSuchElementException : no such element: Unable to locate element: {"method" :"name" ,"selector" :"btnk"}

angular - Util 模块中 util.isNullOrUndefined(object) 的替代方法是什么? Angular 7