RE: [Template-haskell] Hygienic macros and lexical-scoping
Kamil Thanks for your email. | My question is: when binding of variables is resolved? Is it during | translation of quotation, or maybe during expansion of $(...) where the above | code is used? Answer 1: Binding is resolved before any expansion or translation. So in \x -> $(f [| x |]) the occurrence of 'x' is bound indissolubly to the binding '\x', and you can tell that by looking at the source code, before expansion. So if 'f' was defined like this f :: Q Exp -> Q Exp f e = [| \x -> $e |] Then the expression \x -> $(f [| x |]) means the same as \x1 -> \x -> x1 Answer 2: In an extension that is implemented but not described in a published paper (but is described here http://research.microsoft.com/~simonpj/tmp/notes2.ps) we also support a sort of dynamic binding: \x -> $(f (dyn "x")) Here 'dyn' is a function with type dyn :: String -> Q Exp (dyn "x") is a variable that will bind to whatever "x" is in scope where that expression is spliced in. So the expression \x -> $(f (dyn "x")) means the same as \x1 -> \x -> x Notice that this dynamic binding always explicitly involves a string, like "x". You wouldn't expect (dyn "x") to bind indissolubly to the enclosing '\x'. | After reading the paper, I'm convinced that it's former case. But let's | consider: | using x val body = [| do { (var x) <- val; body; (var x).Dispose () } |] | // x is of type string | | [| \y -> do { $(using "x" [| x |]); f y |] | | wouldn't work, and one would have to use gensym by his own to get expected | sematics. I'm sorry, I didn't understand this at all. What is 'using x val body'? What is .Dispose()? Not Template Haskell certainly. | In our system we are shifting binding of variables from quotation translation | to macro expansion phase, so [| x |] can be "catched" inside 'using' | meta-function. Resolving of scoping is done after all executions of macros | are done. It involves quite complex algorithm to tag variables with place | where they come from - we are adapting Dybvig's algorithm into our system, so | we could use most flexible convention introduced by Scheme community. As I say above, TH supports both pre-expansion binding (via plain mentions of a variable), and post-expansion biding (via "dyn"). Whatever you choose to do, let me encourage you to give it a formal description. | As we discussed our design, there was some confusion if macros should | introduce new definitions into program. I noticed, that in Haskell it is done | by special keyword 'splice', and it's forbidden inside quotations. As we | decided to resolve names in late phase, we could introduce definitions in | arbitrary place. Yes, that's right. We discuss this in Section 8 of the above note. (But nested declaration splicing isn't yet implemented.) Some questions arise in that case: | Let's consider following macros (in Nemerle syntax, as I'm more used to it) | | using Foo; /// it introduces functions 'blah' and 'bar' variables | macro m() { <[ def bar() { 1 } ]> } | macro m'() { <[ m(); blah() + bar() ]> } | | does m'() generate | def bar_43 = 1; Foo.blah() + Foo.bar() | or | def bar_43 () { 1 }; Foo.blah() + bar_43() The most systematic story in TH would be that you get the former -- that's lexical scoping. You could get the latter by using (dyn "bar"). But, for top-level declarations, it's very inconvenient to use all the dyn stuff. So in TH we compromise: top level splices lexically shadow previous stuff, so you get the latter definition. We explicitly call this a hack (see section 8.2). Simon
Thank you for your response and clarifying things, Simon.
| using x val body = [| do { (var x) <- val; body; (var x).Dispose () } |] | // x is of type string | | [| \y -> do { $(using "x" [| x |]); f y |] | | wouldn't work, and one would have to use gensym by his own to get
expected
| sematics.
I'm sorry, I didn't understand this at all. What is 'using x val body'? What is .Dispose()? Not Template Haskell certainly.
Well, it supposed to be a function, which takes 'x' (of type string), 'val' and 'body' (being expressions) and creates code which does: - define local variable of name 'x' initialized with 'val' - execute 'body' (it is assumed to have occurrences of 'x' inside, which should be captured by local 'x' - disposes 'x' (it has method 'Dispose', so I think its Haskell counterpart would be calling function 'Dispose' on 'x') This can be implemented in TH with (dyn "x") which you mentioned, but if 'body' is itself passed from another context and 'x' is introduced in f body = [| using "x" Foo $( [| do { dosomething x; body } |] ) |] then some external 'x' in original 'body' might get catched by dynamic binding, which is not hygienic behavior. I'm aware that this can be workaround with manually creating 'x' with newName and using this Name. Our decision to bind variables after macros expansion makes it possible to handle such cases in other way. There are opposite design goals, and I clearly see advantage of TH's one - bind to what you can see, before any expansion. Nevertheless we will try another approach and experiment with such system. To make myself more clear, I will give another example. Unfortunately I'm not able to easily write Haskell counterpart, so I will try to describe what I mean in syntax of our system. (It's more similar to C# / Ocaml, where meta-functions are tagged explicitly at definition, but used like ordinary functions. Quotations are constructing code <[ e_1; e_2; ... ]>, while $ splices value of meta-expressions into enclosing quoted code). We have a following macro macro @for (init, cond, change, body) { <[ $init; def loop () : void { when ($cond) { $body; $change; loop() } }; loop (); ]> } It can be used e.g. for (mutable i <- 0; i < 10; i <- i + 1, printf ("%d\n", i)) If we put it into quotation <[ for (mutable i <- 0; i < 10; i <- i + 1) printf ("%d\n", i) ]> we would not find bindings for 'i' - definition of local 'i' is just parameter of function call, so other parameters are not in its scope and compilation of this quotation would fail. It's quite inconsistent with writing 'for' in plain code, where binding is done after its expansion. This and other examples made us consider binding always after expansion and handling hygiene in other way. This also shifts some error messages to next compilation stage, as you describe in notes.
As I say above, TH supports both pre-expansion binding (via plain mentions of a variable), and post-expansion biding (via "dyn"). Whatever you choose to do, let me encourage you to give it a formal description.
This needs good documentation for sure, as there are many contradictory intuitions at field of names binding. We are trying to design system as easy to use and understand as possible. One again thanks for pointing me revised notes and arguments behind your design. It was quite useful to see more examples and problems that can araise. Kamil Skalski Nemerle developer - http://nemerle.org
participants (2)
-
Kamil Skalski -
Simon Peyton-Jones