2011-07-20 46 views
5

我偶然发现了一个相当简单的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 Point2D = Complex(struct 
    type t = { x : int ; y : int } 
end) 

let (Some location) = Point2D.read db "location" 

似乎有被访问xy领域没有简单的方法,从上述定义:

module Int = Complex(struct 
    type t = int 
end) 

当我与记录工作出现问题Point2D模块之外,如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 *) 
+1

发布可编译代码将非常有助于获得可编译的答案。 –

回答

3

首先,你最后的代码示例中,最后一行,则可能意味着.a而非.x

与您的代码的问题是,与你的方式定义Make仿函数,类型tMade摘要:事实上,仿函数使用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) 
1

的问题是,OCaml的不具有名称来指代类型t的合格部件(在这种情况下的记录,但是同样的问题将存在与正常变体)以外Made。命名无名解决了这个问题:

module F = struct 
    type t = {a : int} 
    let default = { a = 0 } 
end 

module Made = Make(F) 

let _ = (Made.get None).F.a (* <-- WORKS *) 

您也可以明确地声明的函子应用程序外部类型:

type rcd = {a : int} 

module Made = Make(struct 
    type t = rcd 
    let default = { a = 0 } 
end) 

let _ = (Made.get None).a (* <-- WORKS *) 
+0

谢谢你的回答。我可以使用你的第一个解决方案,但是我正在寻找一些比保留模块'F'更优雅的东西来访问字段本身(因为这个模块永远不会用于其他任何东西)。第二个解决方案是行不通的,因为'let default'是生成的,并且会在类型定义之后出现,所以我必须手动将它复制到模块中(实际上,它实际上是多个值,而不仅仅是一个)复制它们非常冗长)。 –

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) 

(好吧,这是不显着的位置,但你还是会得到相同抽象类型MadeA1MakeA2,不像上面的Made1Made2情况下,这是因为现在有这些名字类型:MadeA1.t = Make(A).t。)

+0

很好的解释。为概念命名(“生成性”),即使没有为最初的问题提供一个实际的优雅解决方案,也会有很大的帮助。 –