Mailing list for all users of the OCaml language and system.
 help / color / mirror / Atom feed
From: David Allsopp <dra-news@metastack.com>
To: "pierrchp@free.fr" <pierrchp@free.fr>
Cc: "OCaml List (caml-list@inria.fr)" <caml-list@inria.fr>
Subject: RE: [Caml-list] Module design
Date: Sat, 20 Aug 2011 20:47:15 +0000	[thread overview]
Message-ID: <E51C5B015DBD1348A1D85763337FB6D9C2412549@Remus.metastack.local> (raw)
In-Reply-To: <1313857880.4e4fe158e4d24@imp.free.fr>

Thanks for your reply

pierrchp@free.fr wrote:
> So if I understand you need users to:
>   - be able to customize conenctions ids

Not quite - it's that a given backend may wish to attach additional data to each connection (the processor itself would still use an int to represent the connection). It seems neater to do this in the backend processor (i.e. attach it to the type itself), rather than forcing each backend to maintain a Hashtbl mapping connection IDs to its own data structure.

>   - be able to customize connect or disconnect if they want to
> 
> I think I would keep the idea of having options, and do something like
> this

I guess in simplifying it to create an example, I obscured the fact that there are 13 callbacks and most backends will only override 2-3 of them - which is why I don't like the option approach as much (lots of typing out null stubs). There's also a slight benefit to using "include" with a module filled with defaults - if additional hooks are added, then older backends wouldn't need to be altered (they would simply use the default callbacks).

> type 'a connection =  {connection_id:'a ;
>                       (*some other things that needs to be defined*)
>                        }
>                       (* type of connections *)
> 
> let make_connection connid = {connection_id = connid;
>                             (*some other things that needs to be defined*)
> 			     }
> 
> module type CONN = (* custom connection *)
>   sig
>   val connect: ('a connection -> bool) option
>   val disconnect: ('a connection -> unit) option end

This signature can't work, I think - an actual backend won't be using 'a connection - it will have instantiated a specific one which means that the module won't unify with the signature.

> module type BACKEND = functor (C:CONN) -> sig val connect:'a connection ->
> bool val disconnect:'a connection -> unit end
> 
> 
> module Backend : BACKEND = functor (C:CONN) ->  struct
>    let default_connect c=  (*implement default connect*)
>    let default_disconnect c= (* implement default disconnect *)
> 
>    let connect c = match C.connect with
>          None -> default_connect c
>         |Some f -> f c
> 
>    let disconnect c= match C.disconnect with
>          None -> default_disconnect c
>         |Some f -> f c

This means that the "default" and the actual function are still both included in the final module - I was wondering if there's a way of avoiding that (without using objects) but maybe there just isn't and I shouldn't worry about it! OCaml 3.12 substitution (7.16 in the manual) only allows you to erase types from a module inclusion, not values, sadly.


David


> (* Repeated the pattern matching for more clarity, it can be implemented
> as a function :
>     let analyze default fopt = match fopt with
>          None -> default
>         |Some f -> f
> 
> then you just have to
>  let connect c = (analyze default_connect C.connect) c  let disconnect c =
> (analyze default_disconnect C.disconnect) c
> *)
> 
> end
> 
> 
> -Cheers
> 
> Pierre
> 
> Selon David Allsopp <dra-news@metastack.com>:
> 
> > I'm working on a new version of a framework for a server daemon which
> > can have custom functionality plugged in through different backends.
> > The present version works by passing a record containing lots of
> > options along the lines
> > of:
> >
> > type backend = {b_connect: (connectionID -> bool) option; b_disconnect:
> > (connectionID -> unit) option}
> >
> > which is then passed to a function run which does the actual work,
> > using the callbacks given in the backend record or substituting
> > defaults where None is specified.
> >
> > I'm thinking that a functor would be a much neater way of doing this
> > (and would also allow for passing around more than just a connectionID
> > if
> > required) but wondering what the best way of preserving the ability to
> > have default handlers for functions which a given backend isn't
> interested in.
> >
> > I've not really used the module system beyond trivial functor
> > applications (Set and Map, etc.) but I've come up with the following:
> >
> > (* Framework.ml *)
> >
> > (* Individual connection identifiers *) type connectionID = int
> >
> > (* Wrapper type for custom connections *) module type CONNECTION = sig
> >   type t
> >   val newConnection : connectionID -> t end
> >
> > (* Actual backend type *)
> > module type BACKEND =
> >   sig
> >     include CONNECTION
> >
> >     (* Toy functions, obviously *)
> >     val connect : t -> bool
> >     val disconnect : t -> unit
> >   end
> >
> > (* Default behaviour defined in these two modules *) module Default =
> > struct
> >   (* Default connection information is just the identifier *)
> >   module Connection : CONNECTION = struct
> >     type t = connectionID
> >     let newConnection connectionID = connectionID
> >   end
> >
> >   (* Default functions *)
> >   module Backend (C : CONNECTION) = struct
> >     let connect _ = (* ... *)
> >     let disconnect _ = (* ... *)
> >   end
> > end
> >
> > module Make (Backend : BACKEND) = struct
> >   let run () = (* ... *)
> > end
> >
> > and so an implementation using default connection IDs could be written:
> >
> > module rec MySimpleBackend : Framework.BACKEND = struct
> >   include Framework.Default.Connection
> >
> >   include Framework.Default.Backend(MySimpleBackend)
> >
> >   let connect _ = (* Alternate behaviour *)
> >   (* Default disconnect is fine *)
> > end
> >
> > and one with more complex connectionIDs could be written:
> >
> > module rec MyComplexBackend : Framework.BACKEND = struct
> >   type t = {ci_id : Framework.connectionID; (* ... *) }
> >   let newConnection id = {ci_id = id; (* ... *) }
> >
> >   include Framework.Default.Backend(MyComplexBackend)
> >
> >   let connect {ci_id; (* ... *)} = (* Alternate behaviour *) end
> >
> > This pattern seems to work OK but is there an even neater way I
> > haven't spotted? I'm presuming that in the following:
> >
> > module Foo = struct let x = true let x = false end
> >
> > the compiler doesn't create a module with two fields one of which is
> > inaccessible which would seem to be important (from an aesthetic
> > sense) with having a module of default functions which get "overridden".
> >
> > Any guidance/comment appreciated!
> >
> >
> > David
> >
> >
> > --
> > Caml-list mailing list.  Subscription management and archives:
> > https://sympa-roc.inria.fr/wws/info/caml-list
> > Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> > Bug reports: http://caml.inria.fr/bin/caml-bugs
> >
> >
> 



      parent reply	other threads:[~2011-08-20 20:47 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-08-20 14:31 David Allsopp
2011-08-20 16:39 ` pierrchp
     [not found] ` <1313857880.4e4fe158e4d24@imp.free.fr>
2011-08-20 20:47   ` David Allsopp [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=E51C5B015DBD1348A1D85763337FB6D9C2412549@Remus.metastack.local \
    --to=dra-news@metastack.com \
    --cc=caml-list@inria.fr \
    --cc=pierrchp@free.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox