模块和记录字段

发布于 2024-11-25 08:30:14 字数 1362 浏览 2 评论 0原文

我偶然发现了一个相当简单的 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"

似乎没有简单的方法可以访问上面定义的 xy 字段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 *)

I have stumbled across a rather simple OCaml problem, but I can't seem to find an elegant solution. I'm working with functors that are applied to relatively simple modules (they usually define a type and a few functions on that type) and extend those simple modules by adding additional more complex functions, types and modules. A simplified version would be:

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 

There is no need to give the simple module a name because all its functionality is present in the extended module, and the functions in the simple module are generated by camlp4 based on the type. The idiomatic use of these functors is:

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

The problem appears when I'm working with records:

module Point2D = Complex(struct
  type t = { x : int ; y : int }
end)

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

There seems to be no simple way of accessing the x and y fields defined above from outside the Point2D module, such as location.x or location.Point2D.x. How can I achieve this?

EDIT: as requested, here's a complete minimal example that displays the issue:

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 *)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

眸中客 2024-12-02 08:30:14

让我们看一下所涉及的一些模块的签名。这些是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 = At 是如何保留的(因此 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。)

Let's look at the signature of some of the modules involved. These are the signatures generated by Ocaml, and they're principal signatures, i.e. they are the most general signatures allowed by the theory.

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

Notice how the equation Make(A).t = A.t is retained (so Make(A).t is a transparent type abbreviation), yet Made.t is abstract. This is because Made is the result of applying the functor to an anonymous structure, so there is no canonical name for the argument type in this case.

Record types are generative. At the level of the underlying type theory, all generative types behave like abstract types with some syntactic sugar for constructors and destructors. The only way to designate a generative type is to give its name, either the original name or one that expands to the original name via a series of type equations.

Consider what happens if you duplicate the definition of 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)

You get two different types Made1.t and Made2.t, even though the right-hand sides of the definitions are the same. That's what generativity is all about.

Since Made.t is abstract, it's not a record type. It doesn't have any constructor. The constructors were lost when the structure argument was closed, for a lack of a name.

It so happens that with records, one often wants the syntactic sugar but not the generativity. But Ocaml doesn't have any structural record types. It has generative record types, and it has objects, which from a type theoretical view subsume records but in practice can be a little more work to use and have a small performance penalty.

module Made_object = Make(struct
    type t = <a : int>
    let default = object method a = 0 end
  end)

Or, if you want to keep the same type definition, you need to provide a name for the type and its constructors, which means naming the structure.

module A = struct
    type t = {a : int}
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *)
  end
module MadeA = Make(A)

Note that if you build Make(A) twice, you get the same types all around.

module MadeA1 = Make(A)
module MadeA2 = Make(A)

(Ok, this isn't remarkable here, but you'd still get the same abstract types in MadeA1 and MakeA2, unlike the Made1 and Made2 case above. That's because now there's a name for these types: MadeA1.t = Make(A).t.)

已下线请稍等 2024-12-02 08:30:14

首先,在最后一个代码示例的最后一行中,您可能指的是 .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)

First of all, in your last code sample, last line, you probably mean .a rather than .x.

The problem with your code is that, with the way you define your Make functor, the type t is abstract in Made: indeed, the functors use the TYPE signature which seals {a : int} as an abstract type.

The following design circumvent the issue, but, well, its a different design.

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)
ゞ花落谁相伴 2024-12-02 08:30:14

问题是 OCaml 没有名称来引用 t 类型的限定组件(在本例中是一条记录,但普通变体也会出现同样的问题)。制作。命名未命名解决了问题:

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 *)

The problem is that OCaml doesn't have a name to refer to the qualified components of the type t (in this case a record, but the same problem would be present with normal variants) outside Made. Naming the unnamed solves the problem:

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

module Made = Make(F)

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

You can also declare explicitly the type outside the functorial application:

type rcd = {a : int}

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

let _ = (Made.get None).a (* <-- WORKS *)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文