* [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