From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Original-To: caml-list@sympa.inria.fr Delivered-To: caml-list@sympa.inria.fr Received: from mail1-relais-roc.national.inria.fr (mail1-relais-roc.national.inria.fr [192.134.164.82]) by sympa.inria.fr (Postfix) with ESMTPS id 19A417ED5D for ; Tue, 7 Aug 2012 03:48:15 +0200 (CEST) Received-SPF: None (mail1-smtp-roc.national.inria.fr: no sender authenticity information available from domain of anthony.tavener@gmail.com) identity=pra; client-ip=74.125.82.182; receiver=mail1-smtp-roc.national.inria.fr; envelope-from="anthony.tavener@gmail.com"; x-sender="anthony.tavener@gmail.com"; x-conformance=sidf_compatible Received-SPF: Pass (mail1-smtp-roc.national.inria.fr: domain of anthony.tavener@gmail.com designates 74.125.82.182 as permitted sender) identity=mailfrom; client-ip=74.125.82.182; receiver=mail1-smtp-roc.national.inria.fr; envelope-from="anthony.tavener@gmail.com"; x-sender="anthony.tavener@gmail.com"; x-conformance=sidf_compatible; x-record-type="v=spf1" Received-SPF: None (mail1-smtp-roc.national.inria.fr: no sender authenticity information available from domain of postmaster@mail-we0-f182.google.com) identity=helo; client-ip=74.125.82.182; receiver=mail1-smtp-roc.national.inria.fr; envelope-from="anthony.tavener@gmail.com"; x-sender="postmaster@mail-we0-f182.google.com"; x-conformance=sidf_compatible X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Am0BAKFzIFBKfVK2kGdsb2JhbABFuUkIIgEBAQEJCQ0HFAQjgjkCExkBGx4DEhAHVgERAQUBDkmHWwEDDJoJgmEJA4wjgnGFPwoZJw1XiHEBBQyLXoZNA4hNjHyOLz6EHYE4 X-IronPort-AV: E=Sophos;i="4.77,724,1336341600"; d="scan'208";a="169240745" Received: from mail-we0-f182.google.com ([74.125.82.182]) by mail1-smtp-roc.national.inria.fr with ESMTP/TLS/RC4-SHA; 07 Aug 2012 03:48:05 +0200 Received: by weyx56 with SMTP id x56so3742642wey.27 for ; Mon, 06 Aug 2012 18:48:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:date:message-id:subject:from:to:content-type; bh=/drg3sQ07cgMfX69I4DANxGd4XvArnpIqPeQzO0fTQg=; b=rtHO9J8s9v3MRzwwbKwkVXoTnCo0bxKMscQ4zbJr3lKlu42fMafOFf80F7zKT+2HvA KEsz6Kl1o1ytFTW5drCtu1JaqEd4N4n4qsDLg+5cL8zrsbmrTdJQujzak7SBjqT2utST tepGK2/GfpcuMC482g1uxZCnuhLkzLBLlkafpZPb85CUVgHLjHZG9Uk6i09KZj5NYkYo TNAG3/+j6e5UP0a5mg8qljslGDsBL3UE9kEaHfQSy9aRNnxsFOdaE6GBWjy7gr8zQ1jy sKvtNf2ttcl3JXEYHdrMw8GzHl/h6CXmmePsIo3zMkwTCFbMu1syrJ/Nfh0GU3XpLOhp KN+w== MIME-Version: 1.0 Received: by 10.180.100.131 with SMTP id ey3mr23056105wib.15.1344304084789; Mon, 06 Aug 2012 18:48:04 -0700 (PDT) Received: by 10.223.201.72 with HTTP; Mon, 6 Aug 2012 18:48:04 -0700 (PDT) Date: Mon, 6 Aug 2012 19:48:04 -0600 Message-ID: From: Anthony Tavener To: caml-list@inria.fr Content-Type: multipart/alternative; boundary=f46d0444ec1971a92504c6a32f69 Subject: [Caml-list] A use-case for first-class modules... out of curiosity is there another way? --f46d0444ec1971a92504c6a32f69 Content-Type: text/plain; charset=ISO-8859-1 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 --f46d0444ec1971a92504c6a32f69 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable
Embedding "database" features into other modules.
=
First-class modules are allowing me to neatly unpack table i= mplementations into
other modules, using local types.

This is really quite basic, but I wonder if I could have don= e this before
first-class modules, and without leveraging the obj= ect system? I also don't
recall seeing first-class modules us= ed for something like this.

So, is there another way to do this, aside from the obj= ect system? It's very
much like creating a basic object.

This is an extracted and simplified example...
--------------------

module Db =3D struct

=A0 (* Signature for a single 'table' in the = database, elements of type t. *)
=A0 module type S =3D sig
<= div> =A0 =A0 (* Full implementation relies on a Key module for different key typ= es than 'int' *)
=A0 =A0 type t
=A0 =A0 val get= : int -> t
=A0 =A0 val set : int -> t -> unit
=A0 =A0 val del : int -> unit
=A0 =A0 val iter : (int -> t -> unit) -> unit
=A0 = =A0 val fold : (int -> t -> 'a -> 'a) -> 'a -> &= #39;a
=A0 end

=A0 (* Instantiates storag= e for a table, and returns FC module to interact with the store. *)
=A0 let create_with_default (type s) ?(size=3D19) default =3D
=A0 =A0 (* Full implementation is parameterized by Key and Table modules = *)
=A0 =A0 let h =3D Hashtbl.create size in
=A0 =A0 let= module H =3D struct
=A0 =A0 =A0 type t =3D s
=A0 =A0 =A0 let get (id:int) =3D=A0=
=A0 =A0 =A0 =A0 try Hashtbl.find h id
=A0 =A0 =A0 =A0 = with Not_found -> default
=A0 =A0 =A0 let set id (v:t) =3D Has= htbl.replace h id v
=A0 =A0 =A0 let del id =3D Hashtbl.remove h i= d
=A0 =A0 =A0 let iter f =3D Hashtbl.iter f h
=A0 =A0 =A0 let = fold f init =3D Hashtbl.fold f h init
=A0 =A0 end in
= =A0 =A0 (module H : S with type t =3D s)

end
=

(* An example table... *)
module Location =3D struct
=A0 let unknown =3D "Unknown= "
=A0 include (val (Db.create_with_default unknown) : S with= type t =3D string)
=A0 (* Location might have a bunch of other f= unctionality as well... *)
end

(* and basic usage... *)
# Loca= tion.get 1;;
- : Location.t =3D "Unknown"
# L= ocation.set 1 "Mars";;
- : unit =3D ()
# Loca= tion.get 1;;
- : Location.t =3D "Mars"

---------= -----------
Some background on what this is for: (skip unless you= 're interested!)

I use a "component archi= tecture" 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 fir= st 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 modu= les.
Sloppy, but workable. (The reason I had difficulty is becaus= e 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 &qu= ot;table". Eg. Characteristics, Size, Wounds, Faction,
Inven= tory, etc. So ideally I wanted a convenient way to embed "database&quo= t;
functions into such modules while declaring the implementation of the<= /div>
underlying datastore (hashtable, map, whatever).

This might seem a bit ugly from a functional-programming perspecti= ve, but I've
found components to be quite powerful, and overall helps to constrain = where
and how mutation happens. "Game state" is general= ly in flux -- well, it is
everything variable, and can be compare= d closely with save-game state. Most code
which doesn't update game state can be functional. Actually, it fe= els creepy to
have a variable assignment in the code, since mutat= ion is generally to game-state
and that's handled through a d= atabase. So the resulting style is
functional+database.

=A0-Tony

<= /div> --f46d0444ec1971a92504c6a32f69--