[racket] ok what's wrong there 'syntax-rule' evaluating its operand (??)

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Wed Jan 14 20:53:15 EST 2015

On Jan 14, 2015, at 8:14 PM, Thomas Lynch wrote:

> Matthias thank you for fielding my question, though two things are not clear.  I hope you or someone might clarify.  
> 
> Firstly,  is there a reason to prefer the macro you present over the first one given in the original post.  I believe they both work.  Neither uses the more elegant syntax-rule.


Code that works is fine. Code that clearly explains its intention is better. Since syntactic extensions are (basically) extensions to the grammar, their specification ought to bring across (a) what kind of new expressions programmers may write down and (b) what these new expressions mean. I think separating these cleanly is also important. 

With syntax-case, which isn't all that different from syntax-rules, you get (a) easily: 

  (with-tables stem body ...)

Compare this to your three or four lines that stx apart. When your extension is even more complicated, you definitely want this pattern matching notation because it is close to the way people write grammars and because you can automatically check certain properties (see syntax-parse).  

For (b), constructing a piece of syntax manually with backquote, comma, splice is again much more complicated than writing down a rewrite rule. In particular, you get a really good handle at the manipulation of scope, which is one of the primary functions of syntactic extensions. 

Racket like Scheme like Lisp is about being able abstract boiler plate. Syntax-case abstracts your boiler plate for (a) and (b). 

Ideally, I would like to separate (a) from (b) so that programmers can specify (a) at the module boundary, just like contracts for functions. That's what I started Ryan on and we ended up syntax-parse. It' s a fantastic first step but there is work left to do. Probably another dissertation. 


> Secondly, and this is the question I'm really getting at,  is there a reason that the operands given to syntax-rules must have identifiers within lexical scope at the point of the macro call, rather than lexical scope at the point of their use within the macro?   

Lexical scope is critical for program comprehension. Programmers read programs a lot more often than they write them. Lexical scope guarantees that when they read programs all identifiers are resolved (bound) to a declaration (binding position) that can be found by reading the text -- not running the program. By finding it in the text w/o running it, you have a better chance of predicting what the program does when you run it. 

Macros rewrite program text. In the process, they substitute use-site code into macro-definition code and macro-definition code is substituted into use-site code. Each substitution may affect lexical scope when performed without respect to lexical scope. Each substitution may thus bind variables to binding occurrences that you can only figure out by running code. Enforcing that 

 	use-site variables are bound by use-context
	macro-site variables are bound by macro-contexts 

___by default___ is called hygienic expansion and almost always gives you what you want. 

On rare occasion, an extension of Racket's grammar also needs to break these default rules. syntax-case/parse allow programmers to break those rules in a way that is still easy to read off from the code. 

That's why it's the right way to go about syntax extensions. -- Matthias






> 
> Off hand the latter seems to be the proper behavior for a macro,  i.e. perhaps this is a bug?  Can anyone here tell me why it behaves like this?
> 
> 
> On Thu, Jan 15, 2015 at 4:12 AM, Matthias Felleisen <matthias at ccs.neu.edu> wrote:
> 
> You want something like this:
> 
> (define-syntax (with-tables stx)
>   (syntax-case stx ()
>     [(with-tables stem body ...)
>      (let ([table-author (datum->syntax stx 'table-author)]
>            ;; ... ditto for other identifiers for which you wish to break lexical scope
>            )
>        #`(let ([table-publication (string-append stem "_publication")]
>                [#,table-author (string-append stem "_author")]
>                [table-bridge-publication-author (string-append stem "_bridge_publication_author")]
>                [table-unique-counters (string-append stem "_unique_counters")])
>            body ...))]))
> 
> (with-tables "x" table-author)
> 
> ;; ---
> 
> To achieve this with syntax-rules would be, well, hard.
> 
> ;; ---
> 
> The accepted way of writing this macro is:
> 
> (define-syntax (with-tables stx)
>   (syntax-case stx ()
>     [(with-tables stem (table-author
>                         ;; ... add other names you wish to bind in body
>                         )
>                   body ...)
>      #`(let ([table-publication (string-append stem "_publication")]
>              [table-author (string-append stem "_author")]
>              [table-bridge-publication-author (string-append stem "_bridge_publication_author")]
>              [table-unique-counters (string-append stem "_unique_counters")])
>            body ...)]))
> 
> (with-tables "x" (table-author) table-author)
> 
> 
> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20150114/0476f5dc/attachment.html>

Posted on the users mailing list.