From mboxrd@z Thu Jan 1 00:00:00 1970 Received: (from majordomo@localhost) by pauillac.inria.fr (8.7.6/8.7.3) id IAA05882; Mon, 14 Jan 2002 08:12:21 +0100 (MET) X-Authentication-Warning: pauillac.inria.fr: majordomo set sender to owner-caml-list@pauillac.inria.fr using -f Received: from concorde.inria.fr (concorde.inria.fr [192.93.2.39]) by pauillac.inria.fr (8.7.6/8.7.3) with ESMTP id IAA06078 for ; Mon, 14 Jan 2002 08:12:20 +0100 (MET) Received: from c007.snv.cp.net (c007-h011.c007.snv.cp.net [209.228.33.217]) by concorde.inria.fr (8.11.1/8.11.1) with SMTP id g0E7CJf17898 for ; Mon, 14 Jan 2002 08:12:19 +0100 (MET) Received: (cpmta 22879 invoked from network); 13 Jan 2002 23:12:17 -0800 Received: from 65.187.194.217 (HELO vaiobambino) by smtp.directvinternet.com (209.228.33.217) with SMTP; 13 Jan 2002 23:12:17 -0800 X-Sent: 14 Jan 2002 07:12:17 GMT From: "Jeff Henrikson" To: Subject: [Caml-list] record labels of record scope using camlp4 Date: Mon, 14 Jan 2002 02:28:02 -0500 Message-ID: <000101c19ccc$ffe8e8c0$0b01a8c0@mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit X-Priority: 3 (Normal) X-MSMail-Priority: Normal X-Mailer: Microsoft Outlook 8.5, Build 4.71.2173.0 Importance: Normal X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2615.200 Sender: owner-caml-list@pauillac.inria.fr Precedence: bulk It seems like a lot of people dislike the fact that record labels have module scope instead of record scope. Looking at the identifier style in the ocaml source we see the symmetry behind this design choice: (* Type expressions for the core language *) type core_type = { ptyp_desc: core_type_desc; ptyp_loc: Location.t } and core_type_desc = Ptyp_any | Ptyp_var of string | Ptyp_arrow of label * core_type * core_type | Ptyp_tuple of core_type list | Ptyp_constr of Longident.t * core_type list | Ptyp_object of core_field_type list | Ptyp_class of Longident.t * core_type list * label list | Ptyp_alias of core_type * string | Ptyp_variant of row_field list * bool * label list option and core_field_type = { pfield_desc: core_field_desc; pfield_loc: Location.t } I.e. an abbreviated type always prefixes constructors or labels. But I find this annoying when records get long, since to type a pattern for a record with n entries requires specifying the type n times! It is even tempting to accept the incentive of creating short type abbreviations to resist typing a long identifier n times. And eliminating the type from the labels entirely doesn't work if you have lots of fields called generic things like "name" and "address". On the other hand, I wouldn't have a huge problem with this if it were all I had to deal with was: var.Type_label *So* I took the chance to learn some camlp4 and wrote a macro that implements declarations and patterns having record-local scope like this: type person = LOCAL {name:string; addr: string};; let p = Person{name="Joe";addr="1 Broadway Ave"};; match p with Person{name=n;addr=a} -> (n,a);; It does this by prepending the name of the type onto the front of the identifiers and then automatically adding them back on the patterns, that is: type person = { person_name : string; person_addr : string; } so of course you are still stuck with: p.person_name but it seems a small price to pay. The extension properly handles recursive declarations, eg: type person = LOCAL {name:string; addr:string; friends:plist} and plist = Plist_person of (person * plist) | List_End;; _The bad news_ Okay, so there are some problems. First bad news: the code runs great in the toplevel, but that's because camlp4 extends grammars interactively there. Apparently in batch compilation, any EXTEND construct only takes effect at the end of the file, not at the end of a statement. I remember reading this in the manual somewhere, though I don't seem to be able to find it now. And every time you use the LOCAL syntax above, it defines a new macro for the constructor. So the macro won't be visible until the file is done parsing. A workaround might be to stuff all typedefs in one file and then #load them into another. I'd have to think about the consequences. It smells funny. Second bad news: the scoping isn't quite right. The types will be in module scope, but their constructor macros will be global. The good news is that the macros themselves don't know about the members of the types at all. They just syntactically go pasting stuff onto them. So concievably if you had two records with the same names in different modules, nothing bad would happen. Again, it smells funny. _To Do_ Besides working out the practical problems above, I'd like to see more symmetry between function labels and record labels, eg default values. Labels are great if you are creating an API to be called, but they do nothing to help if you are creating a callback API. As in "Here are a bunch of parameters, you don't have to use/know them all if you don't want." Adding default values seems feasible, but it would exacerbate the macro scoping problem. Here's the source below. You can evaluate it in the toplevel. Have fun. Jeff Henrikson PS- > PS> I guess I can understand Xavier, sometimes some people complain > about so-called bugs which are in facts features, and for features > such as the ``cannot use record labels as I would have wished to'' it > is just too often. Apparently camlp4 has, or nearly has, the power to supersede such quibbling. :-) ---------------------------------------------------------------- #load "camlp4o.cma";; (* toploop only *) #load "q_MLast.cmo";; #load "pa_extend.cmo";; open Pcaml;; let loc = (0,0);; (* toploop only *) let prepend_id name labels = let helper2 v = match v with <:patt<$lid:x$>> -> let y = name ^ "_" ^ x in <:patt<$lid:y$>> | _ -> v in let helper1 = fun (patt,expr) -> (helper2 patt,expr) in List.map helper1 labels;; let make_record_constructor name = let lname = String.uncapitalize name in let uname = String.capitalize name in let lbl_expr_list = (Obj.magic (Grammar.Entry.find expr "lbl_expr_list") : (MLast.patt * MLast.expr) list Grammar.Entry.e) in let lbl_patt_list = (Obj.magic (Grammar.Entry.find patt "lbl_patt_list") : (MLast.patt * MLast.patt) list Grammar.Entry.e) in EXTEND expr: LEVEL "simple" [ [ $uname$; "{"; memb = LIST1 lbl_expr_list SEP ";" ; "}" -> (let memb0= prepend_id lname (List.hd memb) in <:expr<{$list:memb0$}>>) ] ]; patt: LEVEL "simple" [ [ $uname$; "{"; memb = LIST1 lbl_patt_list SEP ";" ; "}" -> (let memb0= prepend_id lname (List.hd memb) in <:patt<{$list:memb0$}>>) ] ]; END; ;; (* test: make_record_constructor "bogus";; type bogus = {bogus_foo:string;bogus_bar:string};; Bogus{foo="happy";bar="sad"};; *) let type_declaration = (Obj.magic (Grammar.Entry.find str_item "type_declaration") : MLast.type_decl Grammar.Entry.e);; let type_parameters = (Obj.magic (Grammar.Entry.find type_declaration "type_parameters") : ((string * (bool * bool)) list) Grammar.Entry.e);; let type_patt = (Obj.magic (Grammar.Entry.find type_declaration "type_patt") : (MLast.loc * string) Grammar.Entry.e);; let type_kind = (Obj.magic (Grammar.Entry.find type_declaration "type_kind") : MLast.ctyp Grammar.Entry.e);; let constrain = (Obj.magic (Grammar.Entry.find type_declaration "constrain") : ((MLast.ctyp * MLast.ctyp)) Grammar.Entry.e);; let prepend_id_t name tk = let helper = fun (loc,s,b,t) -> (loc,name ^ "_" ^ s,b,t) in match tk with <:ctyp< { $list:ldl$ }>> -> let ldl2 = List.map helper ldl in <:ctyp< { $list:ldl2$ }>> | _ -> tk;; EXTEND type_declaration: [ [ tpl = type_parameters; n = type_patt; "="; "LOCAL"; tk = type_kind; cl = LIST0 constrain -> match n with (loc,s) -> make_record_constructor s; (n, tpl, prepend_id_t s tk, cl) ] ] ; END;; (* example: *) type person = LOCAL {name:string; addr: string};; let p = Person{name="Joe";addr="1 Broadway Ave"};; match p with Person{name=n;addr=a} -> (n,a);; (* this sort of thing works too: *) type person = LOCAL {name:string; addr:string; friends:plist} and plist = Plist_person of (person * plist) | List_End;; ------------------- Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr