开发者

Modules and record fields

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开发者_如何学Go 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 *)


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


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)


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜