In this particular case this is just a case of existentially quantified. You can do it with first-class module or gadt (both are convenient), or with a clever encoding which only uses record and polymorphic fields. You can read all about it here : http://caml.inria.fr/pub/ml-archives/caml-list/2004/01/52732867110697f55650778d883ae5e9.fr.html On 7 August 2012 03:48, Anthony Tavener wrote: > Embedding "database" features into other modules. > > First-class modules are allowing me to neatly unpack table implementations > into > other modules, using local types. > > This is really quite basic, but I wonder if I could have done this before > first-class modules, and without leveraging the object system? I also don't > recall seeing first-class modules used for something like this. > > So, is there another way to do this, aside from the object system? It's > very > much like creating a basic object. > > This is an extracted and simplified example... > -------------------- > > module Db = struct > > (* Signature for a single 'table' in the database, elements of type t. *) > module type S = sig > (* Full implementation relies on a Key module for different key types > than 'int' *) > type t > val get : int -> t > val set : int -> t -> unit > val del : int -> unit > val iter : (int -> t -> unit) -> unit > val fold : (int -> t -> 'a -> 'a) -> 'a -> 'a > end > > (* Instantiates storage for a table, and returns FC module to interact > with the store. *) > let create_with_default (type s) ?(size=19) default = > (* Full implementation is parameterized by Key and Table modules *) > let h = Hashtbl.create size in > let module H = struct > type t = s > let get (id:int) = > try Hashtbl.find h id > with Not_found -> default > let set id (v:t) = Hashtbl.replace h id v > let del id = Hashtbl.remove h id > let iter f = Hashtbl.iter f h > let fold f init = Hashtbl.fold f h init > end in > (module H : S with type t = s) > > end > > (* An example table... *) > module Location = struct > let unknown = "Unknown" > include (val (Db.create_with_default unknown) : S with type t = string) > (* Location might have a bunch of other functionality as well... *) > end > > (* and basic usage... *) > # Location.get 1;; > - : Location.t = "Unknown" > # Location.set 1 "Mars";; > - : unit = () > # Location.get 1;; > - : Location.t = "Mars" > > -------------------- > Some background on what this is for: (skip unless you're interested!) > > I use a "component architecture" with most games -- basically a database of > properties keyed off "game object IDs". I thought this was a very powerful > feature in old MUDs/MUSHs. It's one of the first things I tried making > when I > started in OCaml, but I had some difficulties and ended up explicity > instantiating hashtables or maps in the global context of various modules. > Sloppy, but workable. (The reason I had difficulty is because I was trying > to > create a database of tables which were created at runtime -- not statically > known.) > > Recently I decided to fix this mess. I had many modules, each which tended > to > have a corresponding "table". Eg. Characteristics, Size, Wounds, Faction, > Inventory, etc. So ideally I wanted a convenient way to embed "database" > functions into such modules while declaring the implementation of the > underlying datastore (hashtable, map, whatever). > > This might seem a bit ugly from a functional-programming perspective, but > I've > found components to be quite powerful, and overall helps to constrain where > and how mutation happens. "Game state" is generally in flux -- well, it is > everything variable, and can be compared closely with save-game state. > Most code > which doesn't update game state can be functional. Actually, it feels > creepy to > have a variable assignment in the code, since mutation is generally to > game-state > and that's handled through a database. So the resulting style is > functional+database. > > -Tony > >