caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] Module design
@ 2011-08-20 14:31 David Allsopp
  2011-08-20 16:39 ` pierrchp
       [not found] ` <1313857880.4e4fe158e4d24@imp.free.fr>
  0 siblings, 2 replies; 3+ messages in thread
From: David Allsopp @ 2011-08-20 14:31 UTC (permalink / raw)
  To: OCaml List

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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [Caml-list] Module design
  2011-08-20 14:31 [Caml-list] Module design David Allsopp
@ 2011-08-20 16:39 ` pierrchp
       [not found] ` <1313857880.4e4fe158e4d24@imp.free.fr>
  1 sibling, 0 replies; 3+ messages in thread
From: pierrchp @ 2011-08-20 16:39 UTC (permalink / raw)
  To: David Allsopp; +Cc: OCaml List

Sorry about the duplicates, but I didn't "reply all".

Here is how I would do it:

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 =
  sig
  val connect: ('a connection -> bool) option
  val disconnect: ('a connection -> unit) option
end

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= false (*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
 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
>
>



^ permalink raw reply	[flat|nested] 3+ messages in thread

* RE: [Caml-list] Module design
       [not found] ` <1313857880.4e4fe158e4d24@imp.free.fr>
@ 2011-08-20 20:47   ` David Allsopp
  0 siblings, 0 replies; 3+ messages in thread
From: David Allsopp @ 2011-08-20 20:47 UTC (permalink / raw)
  To: pierrchp; +Cc: OCaml List (caml-list@inria.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
> >
> >
> 



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2011-08-20 20:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-08-20 14:31 [Caml-list] Module design David Allsopp
2011-08-20 16:39 ` pierrchp
     [not found] ` <1313857880.4e4fe158e4d24@imp.free.fr>
2011-08-20 20:47   ` David Allsopp

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).