[racket] Again on bindings visibility in eval

From: Thomas Chust (chust at web.de)
Date: Thu Jul 14 13:21:41 EDT 2011

Markku Rontu wrote:
> [...]
> I'm sure the section 11 does go through the relevant issues, but doesn't
> feel like a good introduction to the matter. I can theorise that I can
> manipulate these syntax objects with plain old functions but nowhere
> does it seem to show a complete useful case. What I'm missing is an
> example that contains all the bits but that is not as complicated like
> the Racket sources themselves. Maybe I just didn't find it?
> [...]

Hello Markku,

as others have already written it is easier and more rewarding to use
syntax-case and other syntax related libraries available in Racket to
write complex macros rather than to manipulate syntax objects by hand.
Probably that is the case why the former functionality is more
prominently featured in the documentation and the latter is left for the
experts that are able to understand everything after glancing once at a
dry reference document ;-)

Anyway, there is nothing magic about syntax objects, they are just
wrappers around regular S-expression datums that add context
information. To unwrap the data in a syntax object you use syntax-e.
However, the data structure you get is likely to contain more syntax
objects since context information is attached to every cons cell, every
vector entry, every hash table value, etc.

Therefore there are additional handy tools to access syntax objects: If
you want to discard lexical context entirely and just turn a syntax
object into data you can use syntax->datum, if you want to turn syntax
representing a list into a list of syntax objects you can use syntax->list.

For the other direction, to wrap some data into a syntax object, you use
datum->syntax and supply an existing syntax object to provide the
context for the new synthetic expression. In addition there is a
quasiquote analogue for syntax objects that you can use.

Using the syntax quasiquote notation respects hygiene by default, but
unquoting some manually constructed object with the right context you
can selectively break hygiene. Since you don't have to bother about
manual generation of fresh identifiers and proper namespace references
at all and still get hygienic behaviour by default, you can focus on the
few points where you want to break hygiene. Therefore I even claim that
this programming style will still be easier and less cluttered with
boilerplate code in Racket than in Common Lisp or Clojure unless you
disregard hygiene issues entirely.

I'll give you an example implementation of the classical anaphoric if
macro that selectively introduces an identifier called `it' bound to the
result of the test expression into the lexical context of the
conditional's then branch and apart from that behaves just like the
normal `if':

  #lang racket/base
  (require
   (for-syntax racket/base))

  (define-syntax (aif stx)
    (cond
      ;; We need one "special" command to unwrap the syntax object
      ;; holding the whole statement ...
      [(syntax->list stx)
       => (lambda (forms)
            (if (= (length forms) 4)
                (let ([test-expr (cadr forms)]
                      [then-expr (caddr forms)]
                      [else-expr (cadddr forms)])
                  ;; ... and one "special" command to introduce the
                  ;; identifier `it' into the correct context ...
                  (let ([$it (datum->syntax then-expr 'it)])
                    ;; ... however other languages would need a bunch
                    ;; of magical incantations to make cond, else and
                    ;; λ refer to the right things reliably, which we
                    ;; get for free.
                    #`(cond
                        [#,test-expr => (λ (#,$it) #,then-expr)]
                        [else #,else-expr])))
                (raise-syntax-error
                 #f "expected test, then and else clauses" stx)))]
      [else
       (raise-syntax-error #f "expected list of expressions" stx)]))

  (provide
   aif)

For comparison, I also include an implementation to the same effect but
using a nicer encapsulation of the break of hygiene, thanks to a helper
module provided with Racket:

  #lang racket/base
  (require
   (for-syntax racket/base srfi/26)
   racket/stxparam)

  ;; We declare the identifier of the special variable explicitly and
  ;; we also export it later
  (define-syntax-parameter it
    (cut raise-syntax-error
     #f "implicit value used outside anaphoric if" <>))

  (define-syntax aif
    (syntax-rules ()
      [(aif test-expr then-expr else-expr)
       (cond
         [test-expr
          => (λ (v)
               ;; The syntax-parameterize form takes care of making the
               ;; special variable refer to the right thing in the
               ;; context of the macro expasion
               (syntax-parameterize ([it (syntax-id-rules () [it v])])
                 then-expr))]
         [else
          else-expr])]))

  (provide
   it aif)

I hope the quick summary and examples are helpful :-)

Ciao,
Thomas


-- 
When C++ is your hammer, every problem looks like your thumb.


Posted on the users mailing list.