From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail1-relais-roc.national.inria.fr (mail1-relais-roc.national.inria.fr [192.134.164.82]) by walapai.inria.fr (8.13.6/8.13.6) with ESMTP id p7KKlVsw004330 for ; Sat, 20 Aug 2011 22:47:32 +0200 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: ArkCADEcUE5RZ90xlGdsb2JhbABBmEGPUxQBAQEBCQsJCRQDIoFAAQEBAQIBJxM0CwULAgEIGAoDERAyExIBAQQOBQgKCIdXAgK5BYVpXwSTFJB2 X-IronPort-AV: E=Sophos;i="4.68,256,1312149600"; d="scan'208";a="116405649" Received: from mtaout03-winn.ispmail.ntl.com ([81.103.221.49]) by mail1-smtp-roc.national.inria.fr with ESMTP; 20 Aug 2011 22:47:21 +0200 Received: from aamtaout02-winn.ispmail.ntl.com ([81.103.221.35]) by mtaout03-winn.ispmail.ntl.com (InterMail vM.7.08.04.00 201-2186-134-20080326) with ESMTP id <20110820204720.RHJL17535.mtaout03-winn.ispmail.ntl.com@aamtaout02-winn.ispmail.ntl.com>; Sat, 20 Aug 2011 21:47:20 +0100 Received: from romulus.metastack.com ([81.102.132.77]) by aamtaout02-winn.ispmail.ntl.com (InterMail vG.3.00.04.00 201-2196-133-20080908) with ESMTP id <20110820204720.FINO5924.aamtaout02-winn.ispmail.ntl.com@romulus.metastack.com>; Sat, 20 Aug 2011 21:47:20 +0100 Received: from remus.metastack.local ([172.16.0.1]) by romulus.metastack.com (8.14.2/8.14.2) with ESMTP id p7KKlGqL021788 (version=TLSv1/SSLv3 cipher=AES128-SHA bits=128 verify=FAIL); Sat, 20 Aug 2011 21:47:17 +0100 Received: from Remus.metastack.local ([fe80::547c:3c42:e1da:eda2]) by Remus.metastack.local ([fe80::547c:3c42:e1da:eda2%10]) with mapi id 14.01.0289.001; Sat, 20 Aug 2011 21:47:16 +0100 From: David Allsopp To: "pierrchp@free.fr" CC: "OCaml List (caml-list@inria.fr)" Thread-Topic: [Caml-list] Module design Thread-Index: AcxfLFsQyRluzwEKS3Ca8MjKG/yH7AAIdqkAAApXhWA= Date: Sat, 20 Aug 2011 20:47:15 +0000 Message-ID: References: <000701cc5f45$caa14310$5fe3c930$@metastack.com> <1313857880.4e4fe158e4d24@imp.free.fr> In-Reply-To: <1313857880.4e4fe158e4d24@imp.free.fr> Accept-Language: en-GB, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [212.183.128.46] Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Organization: MetaStack Solutions Ltd. X-Scanned-By: MIMEDefang 2.65 on 81.102.132.77 X-Cloudmark-Analysis: v=1.1 cv=JvdXmxIgLJv2/GthKqHpGJEEHukvLcvELVXUanXFreg= c=1 sm=0 a=lNguibf388UA:10 a=cTs9vV391PwA:10 a=kj9zAlcOel0A:10 a=xqWC_Br6kY4A:10 a=SGJpDYsEAAAA:8 a=ZOzjf2MOAAAA:8 a=CjxXgO3LAAAA:8 a=Cygs1a5GlCO781MVpKsA:9 a=9R--TNLeAEhHfARuiv8A:7 a=CjuIK1q_8ugA:10 a=CY5DXcIBj0AA:10 a=dTth3pfvbawA:10 a=CQaipkGsLxe3lLDj:21 a=HMjNSfXXv34g_w-m:21 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117 Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from quoted-printable to 8bit by walapai.inria.fr id p7KKlVsw004330 Subject: RE: [Caml-list] Module design 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 : > > > 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 > > > > >