[wg-camlp4] Matching on concrete syntax (was: Re: Camlp4 uses)

Alain Frisch alain.frisch at lexifi.com
Fri Mar 29 14:35:03 GMT 2013


On 03/29/2013 02:46 PM, Gabriel Scherer wrote:
> Would it be possible to use the extension mechanism itself for
> lightweight quasiquotations?
>
> I did a small experiment with -ppx recently (
> http://gallium.inria.fr/blog/a-library-to-record-ocaml-backtraces/smartcatch_ppx.ml,
> as a contribution to Jacques-Henri Jourdan's work on
> http://gallium.inria.fr/blog/a-library-to-record-ocaml-backtraces/ ),
> and frankly the absence of quasiquotations felt like a pain in the ass.
> Compare the camlp4 part of the relevant code:
>
>    let add_debug_expr _loc patvar e =
>      <:expr<
>          let _ = ExceptionHandling.register $lid:patvar$ in
>          $e$
>      >>
>
> with the Ast_rewriter equivalent (the recommended solution alongside
> -ppx right now):
>
> let add_register patvar body =
>    let open Ast_mapper in
>    let register_fun = Location.mknoloc (Longident.parse "ExceptionHandling.register") in
>    (* let _ = <register_fun> <patvar> in <body> *)
>    E.(let_ Nonrecursive
>         [P.any (), apply (ident register_fun)  ["", ident patvar]]
>         body)


I'd write it as:


let add_register e body =
   let_in [pany, app (evar "ExceptionHandling.register") [e]] body


which does not look so bad.  This relies on the following definitions, 
which could go e.g. in a Ast_helper.Convenience module:

let evar s = E.ident (mknoloc (Longident.parse s))
let let_in l body = E.let_ Nonrecursive l b
    (* maybe with an optional argument for the recursive case *)
let pany = P.any ()
let app f args = apply f (List.map (fun a -> "", a) args)



The rest of the code is interesting as well:

Camlp4:

   value rec map_handler =
     let patvar = "__exn" in
     fun
     [ <:match_case at _loc< $m1$ | $m2$ >> ->
         <:match_case< $map_handler m1$ | $map_handler m2$ >>
     | <:match_case at _loc< $p$ when $w$ -> $e$ >> ->
         <:match_case at _loc<
           ($p$ as $lid:patvar$) when $w$ -> $add_debug_expr _loc patvar 
e$ >>
     | m -> m ];

   value filter = object
     inherit Ast.map as super;
     method expr = fun
     [ <:expr at _loc< try $e$ with [ $h$ ] >> ->
       <:expr< try $e$ with [ $map_handler h$ ] >>
     | x -> super#expr x ];
   end;


PPX:

   method expr e =
     let e = super#expr e in
     { e with pexp_desc =
         match e.pexp_desc with
           | Pexp_try (body, handler) ->
             let instrument_case (pat, body) =
               let patvar_str =  "__exn" in
               let patvar = Location.mknoloc (Longident.parse patvar_str) in
               let pat = { pat with ppat_desc =
                 Ppat_alias (pat, Location.mknoloc patvar_str) } in
               (pat, add_register patvar body) in
             Pexp_try (body, List.map instrument_case handler)
           | other -> other
     }


This might be a matter of taste, but I prefer the PPX version, which I 
can read only by knowing about the Parsetree (which is required anyway 
to write any such code), a normal OCaml library.  The quotation and 
anti-quotations in the Camlp4 version look very noisy, and it relies on 
a syntax I'm not familiar with (revised syntax) and syntactic extensions 
(quotations/antiquotations), with their own conventions 
("<:match_case<", $lid:$).  Since I'm not writing extensions every day, 
I really prefer having to learn how to use simple OCaml data types and 
libraries (Parsetree, Ast_helper) rather than to learn new syntax and 
new concepts.  Also, I'd write the code above as:

(* --> in Ast_helper.Convenience *)
let palias p x = P.alias p (mknoloc x)
let evar s = E.ident (mknoloc (Longident.parse s))
...


   method expr e =
     let e = super#expr e in
     match e.pexp_desc with
     | Pexp_try (body, handler) ->
         let instrument_case (pat, body) =
           (palias pat "__exn", add_register (evar "__exn") body)
         in
         {e with pexp_desc = Pexp_try (body, List.map instrument_case 
handler)}
     | e -> e

> But maybe we
> can have quasiquotations with the current extension mechanism?
>
> let add_register patvar body =
>    [%quote
>      let _ = ExceptionHandling.register [%anti patvar] in [%anti body]
>    ]

Implementing this "quote" expander is not very difficult, just a little 
bit tedious (and this can be automated by parsing the definition of the 
Parsetree).  Note that nothing forces to use [%anti x] for 
antiquotations.  We could very well decide on a more lightweight 
convention, like prefixing identifiers, or using a dedicated operator:


     [%quote
       let _ = ExceptionHandling.register __patvar in __body
     ]

     [%quote
       let _ = ExceptionHandling.register !!patvar in !!body
     ]


But this approach would work nicely only for writing expressions or 
patterns on OCaml expressions, not on other syntactic categories 
(because the content of an extension node is an expression).  The 
problem is that AST-manipulating code tend to require to work a lot with 
many different categories (like "match cases") even to build expressions.

Quasi-quotations would be useful if the expanders had to generate big 
fragments of mostly static code, with only a few "dynamic" placeholders. 
  In my experience, this is rarely the case: you assemble the resulting 
OCaml code by combining many small fragments generated programmatically. 
  For these cases, a nice library of "AST constructors" seems better to me.



-- Alain


More information about the wg-camlp4 mailing list