Thanks Arnaud!
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 <anthony.tavener@gmail.com> wrote:Embedding "database" features into other modules.First-class modules are allowing me to neatly unpack table implementations intoother modules, using local types.This is really quite basic, but I wonder if I could have done this beforefirst-class modules, and without leveraging the object system? I also don'trecall seeing first-class modules used for something like this.So, is there another way to do this, aside from the object system? It's verymuch 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 tval get : int -> tval set : int -> t -> unitval del : int -> unitval iter : (int -> t -> unit) -> unitval fold : (int -> t -> 'a -> 'a) -> 'a -> 'aend(* 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 inlet module H = structtype t = slet get (id:int) =try Hashtbl.find h idwith Not_found -> defaultlet set id (v:t) = Hashtbl.replace h id vlet del id = Hashtbl.remove h idlet iter f = Hashtbl.iter f hlet fold f init = Hashtbl.fold f h initend in(module H : S with type t = s)end(* An example table... *)module Location = structlet 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 ofproperties keyed off "game object IDs". I thought this was a very powerfulfeature in old MUDs/MUSHs. It's one of the first things I tried making when Istarted in OCaml, but I had some difficulties and ended up explicityinstantiating hashtables or maps in the global context of various modules.Sloppy, but workable. (The reason I had difficulty is because I was trying tocreate a database of tables which were created at runtime -- not staticallyknown.)Recently I decided to fix this mess. I had many modules, each which tended tohave 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 theunderlying datastore (hashtable, map, whatever).This might seem a bit ugly from a functional-programming perspective, but I'vefound components to be quite powerful, and overall helps to constrain whereand how mutation happens. "Game state" is generally in flux -- well, it iseverything variable, and can be compared closely with save-game state. Most codewhich doesn't update game state can be functional. Actually, it feels creepy tohave a variable assignment in the code, since mutation is generally to game-stateand that's handled through a database. So the resulting style isfunctional+database.-Tony