[racket] Macros and literal-id

From: Marco Maggi (marco.maggi-ipsu at poste.it)
Date: Tue Aug 9 02:40:10 EDT 2011

Harry Spier wrote:

> And if  not, would  it be possible  for someone to  give a
> simple example of its use.

I will use R6 Scheme here.  Scheme defines the IF syntax as:

   (if ?test ?consequent ?alternate)

where the only "language  keyword" is IF itself.  Many other
languages use  a version with  "then" and "else",  which are
also "reserved keywords" for  those languages; Scheme has no
reserved keywords.

  A Scheme IF  with THEN and ELSE, let's  call it IF*, would
look like:

   (if ?test (then ?consequent) (else ?alternate))

we can define it with the following simple SYNTAX-RULES:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules ()
    ((_ ?test (?then ?consequent) (?else ?alternate))
     (if ?test ?consequent ?alternate))))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
--| yeah

but notice that in this definition the pattern is:

   (_ ?test (?then ?consequent) (?else ?alternate))

and   the  identifiers  ?THEN   and  ?ELSE   become  pattern
variables,  with  no  validation  of  their  value;  so  the
following macro use also works with no error:

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))

that is in the template of the first SYNTAX-RULES arm:

* the  identifier ?THEN  is a  pattern variable  bound  to a
  syntax object holding the number 123;

* the  identifier ?ELSE  is a  pattern variable  bound  to a
  syntax object holding the number 456;

we can verify it with the following modified program:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules ()
    ((_ ?test (?then ?consequent) (?else ?alternate))
     (display ?then))))

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
--| 123

  This  is not  what we  want: we  would like  the  macro to
verify that the components of the input macro falling in the
positions of ?THEN and ?ELSE in the pattern, are identifiers
having  as  names  the  symbol  THEN and  the  symbol  ELSE.

  Specifically   we   would   like,  in   those   positions,
identifiers being  FREE-IDENTIFIER=? to "auxiliary keywords"
THEN and ELSE that we have defined.  We could do such a test
using SYNTAX-CASE:

#!r6rs
(import (except (rnrs) else))

;; Define  auxiliary  keywords,  they are  just  identifiers
;; bound to something.
(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))

(define-syntax if*
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?test (?then ?consequent) (?else ?alternate))
       (if (and (identifier? #'?then)
		(free-identifier=? #'?then #'then))
	   (display 'good)
	 (display 'bad))))))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
--| good

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
--| bad

  We  do not  need  to  use SYNTAX-CASE  for  such a  simple
validation,   because  SYNTAX-RULES  offers   the  <LITERAL>
arguments exactly for this  purpose.  So the full version of
IF... THEN... ELSE... is:

#!r6rs
(import (except (rnrs) else))

(define-syntax if*
  (syntax-rules (then else)
    ((_ ?test (then ?consequent) (else ?alternate))
     (if ?test ?consequent ?alternate))))

(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
--| yeah

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
---> syntax error

  The  language (rnrs) already  defines an  auxiliary syntax
ELSE and it  is all right to  use it for IF* so  we can just
do:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules (then else)
    ((_ ?test (then ?consequent) (else ?alternate))
     (if ?test ?consequent ?alternate))))

(define-syntax then (syntax-rules ()))

  Notice that with these  definitions we have created actual
bindings for  THEN and  ELSE in the  lexical context  of the
definition for  IF*, and IF* will recognise  only those.  So
when writing  a library that  exports IF* we have  to export
THEN and ELSE along with it:

#!r6rs
(library (if-star)
  (export if* then else)
  (import (rnrs))
  (define-syntax if*
    (syntax-rules (then else)
      ((_ ?test (then ?consequent) (else ?alternate))
       (if ?test ?consequent ?alternate))))
  (define-syntax then (syntax-rules ())))

and use it as:

#!r6rs
(import (rnrs)
  (if-star))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
--| yeah

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
---> syntax error

HTH
-- 
Marco Maggi


Posted on the users mailing list.