[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

macro uses, macro blocks, and bare keywords in syntax bindings



> ...which would make me have a need to define LET-MSYNTAX-RULES and
> LETREC-MSYNTAX-RULES as well, and anyone who wanted to use
> MSYNTAX-RULES anywhere else would need to write their own
> foo-MSYNTAX-RULES.  This is rather irritating, but I'm almost afraid
> to consider putting a fix of what I mentioned above in this SRFI -- do
> something about macro uses being used as transformers --, as it would
> undoubtedly generate a flame war somehow or other due to potential
> issues with phase separation and such.

Yes, the high-level macro system would be much improved if macro uses,
macro blocks, and bare keywords were allowed in syntax bindings.  And
yes, conflicts with low-level macro systems are the rub.  Last year, I
thought about doing a SRFI on this (it should be a separate SRFI from
a syntax-rules SRFI), but I never got around to it.  The answer might
be some kind of marker that says "this transformer contains high-level
macros only, so please don't complain about phase problems that
shouldn't affect me", and then you could do:

  (let-syntax
      ((foo (syntax-rules ()
              ((foo . rules) (syntax-rules () . rules)))))
    (let-syntax ((bar (high-level-only (foo ((bar x) (- x))))))
      (bar 1)))
  => -1

But maybe foo would need to be marked too, because it is used by bar
-- and maybe there would be problems referring to, say, quasiquote,
within a high-level-only section because the system implements
quasiquote with a low-level macro.  I don't know.  I would need to
look more closely at PLT's (and others') machinery to see if a "no
phase constraints for high-level macros" device could be smoothly
integrated, or if there are good reasons it would never work.

The thing about ellipsis problems is that, in most cases, the real
problem is not the ellipsis problem.  The real issue is "How do you
write an auxiliary macro local to a macro?".  As I've written in the
past, r5rs gives you three options, all unappealing:

  1. Pollute the top-level namespace with a separate define-syntax.
     (i.e., give up on making it local).

  2. Use the "secret-string" approach.  This allows undocumented variant
     forms of the macro.

  3. Expand into a local definition of the auxiliary macro.  This has
     several drawbacks:

     a. The auxiliary macro can only be used by one of the rules.

     b. The auxiliary macro cannot use ellipsis.

     c. The auxiliary macro must use different pattern variable names
        from the main macro, because pattern variable bindings do not shadow.

     d. The auxiliary macro is parsed and recompiled each time the
        main macro is called.

Fixing the ellipsis problem only partially ameliorates option 3,
whereas allowing macro blocks in syntax bindings fixes the whole
problem:

;; The r5rs letrec macro.
;; Note that the auxiliary macro has dots all over the place, and there's no
;; need to escape them.
(define-syntax letrec
  (letrec-syntax
      ((gentemps
	(syntax-rules ()
	  ((gentemps (x y ...)         (temp ...) ((var1 init1) ...) body ...)
	   (gentemps   (y ...) (newtemp temp ...) ((var1 init1) ...) body ...))
	  ((gentemps        ()        (temp1 ...) ((var1 init1) ...) body ...)
	   (let ((var1 'undefined) ...)
	     (let ((temp1 init1) ...)
	       (set! var1 temp1)
	       ...
	       (let () body ...)))))))
    (syntax-rules ()
      ((letrec ((var1 init1) ...) body1 body2 ...)
       (gentemps (var1 ...) () ((var1 init1) ...) body1 body2 ...)))))

Allowing bare keywords in syntax bindings, as in:

   (let-syntax ((q quote)) (q x)) => x

enables you to replace a builtin with an extended version of it, as in:

  ;; Extend define to support currying as in r2rs:
  ;;    (define ((f x) y) . body)
  ;; == (define (f x) (lambda (y) . body))
  ;; == (define f (lambda (x) (lambda (y) . body)))
  (define-syntax define
    (let-syntax ((plain-define define))
      (letrec-syntax
	  ((currying-define
	    (syntax-rules ()
	      ((_ (var-or-prototype . args) . body)
	       (currying-define var-or-prototype (lambda args . body)))
	      ((_ var expr)
	       (plain-define var expr)))))
	currying-define)))

(My expander supports both those extensions.)
 
Something I discovered last year that I never got around to telling
many people is that once you fix the ellipsis problem, it's possible
for the user to partially implement macro blocks in syntax bindings on
his own.

The code below implements define-syntax+, let-syntax+, and
letrec-syntax+, which work just like their standard counterparts, but
allow (let-syntax+ <bindings> <transformer>) blocks and letrec-syntax+
blocks as valid transformers.  Comment out one of the two versions of
reduce-binding (depending on which ellipsis solution you have).

One problem with this solution is that the macros thus defined can
only expand into expressions, not definitions (because the expansion
is wrapped in the let-syntax that we wanted to put the transformer
in).

You could write a similar trifecta (or extend this one) that would
support macros that expand into transformers (like your
msyntax-rules), as long as you are willing to write each such
transformer-producing macro to take an extra k argument, and to expand
into (k expanded-transformer).


-al


;; transformer-macro-blocks.scm: Macro blocks for transformers.
;; Authored 2002, 2003 by Al Petrofsky and released into the public domain.
;;
;; Define-syntax+, let-syntax+, and letrec-syntax+ work like their
;; standard counterparts, but allow (let-syntax+ <bindings> <transformer>)
;; blocks and letrec-syntax+ blocks as valid transformers.

(define-syntax let-syntax+
  (syntax-rules ()
    ((_ bindings . body)
     (reduce-bindings (let-syntax . body) () . bindings))))

(define-syntax letrec-syntax+
  (syntax-rules ()
    ((_ bindings . body)
     (reduce-bindings (letrec-syntax . body) () . bindings))))

(define-syntax define-syntax+
  (syntax-rules ()
    ((_ . binding)
     (reduce-bindings (define-syntax-binding) () binding))))

(define-syntax define-syntax-binding
  (syntax-rules () ((_ (binding)) (define-syntax . binding))))

;; (reduce-bindings (binder . body) () binding ...)
;; expands to (binder reduced-bindings . body), with any macro-block
;; transformers in the bindings replaced by ordinary transformers.
(define-syntax reduce-bindings
  (syntax-rules (let-syntax+ letrec-syntax+)
    ((reduce-bindings bb reduced (key (let-syntax+ bs t)) . unreduced)
     (reduce-binding  bb reduced (key (let-syntax+ bs t)) . unreduced))
    ((reduce-bindings bb reduced (key (letrec-syntax+ bs t)) . unreduced)
     (reduce-binding  bb reduced (key (letrec-syntax+ bs t)) . unreduced))
    ((reduce-bindings bb             reduced  ordinary . unreduced)
     (reduce-bindings bb (ordinary . reduced)          . unreduced))
    ((reduce-bindings (binder . body) reduced)
     (binder reduced . body))))

;; Reduce a macro-block transformer in the first binding.
;; This version uses (... <subtemplate>) ellipsis escapement, as provided
;; by Chez scheme.
(define-syntax reduce-binding
  (syntax-rules ()
    ((_ bb reduced (key (let/letrec-syntax+ bindings trans)) . unreduced)
     (reduce-bindings bb
		      ((key (syntax-rules ()
			      ((_ . args)
			       ((... ...) (let/letrec-syntax+ bindings
					    (let-syntax+ ((t trans))
					      (t . args)))))))
		       . reduced)
		      . unreduced))))

;; Using new-fangled choose-your-own-ellipsis feature from draft SRFI-46.
(define-syntax reduce-binding
  (syntax-rules ()
    ((_ bb reduced (key (let/letrec-syntax+ bindings trans)) . unreduced)
     (reduce-bindings bb
		      ((key (syntax-rules no-ellipsis ()
			      ((_ . args)
			       (let/letrec-syntax+ bindings
				 (let-syntax+ ((t trans))
				   (t . args))))))
		       . reduced)
		      . unreduced))))

;; Examples, adapted from r5rs:

;; Note that I can use the same pattern variable names in do and
;; do-step without any conflict.  Also note that (syntax-rules ()) is
;; a legal syntax-rules transformer with zero rules, which means that
;; any use of it will cause an expansion-time error (a "no pattern
;; matches" error) that will presumably be reported with a message
;; that includes the arguments.
(define-syntax+ do+
  (letrec-syntax+
      ((do-step (syntax-rules ()
		  ((_ var init) var)
		  ((_ var init step) step)
		  ((_ . clause) (syntax-error "Bad do clause: " clause))))
       (syntax-error (syntax-rules ())))
    (syntax-rules ()
      ((do ((var init step ...) ...)
	   (test expr ...)
	 command ...)
       (let loop ((var init) ...)
	 (if test
	     (begin (if #f #f) expr ...)
	     (begin command ... (loop (do-step var init step ...) ...))))))))

;; Note that the auxiliary macro has dots all over the place, with no
;; need to escape them.
(define-syntax+ letrec+
  (letrec-syntax+
      ((gentemps
	(syntax-rules ()
	  ((gentemps (x y ...)         (temp ...) ((var1 init1) ...) body ...)
	   (gentemps   (y ...) (newtemp temp ...) ((var1 init1) ...) body ...))
	  ((gentemps        ()        (temp1 ...) ((var1 init1) ...) body ...)
	   (let ((var1 'undefined) ...)
	     (let ((temp1 init1) ...)
	       (set! var1 temp1)
	       ...
	       (let () body ...)))))))
    (syntax-rules ()
      ((letrec ((var1 init1) ...) body1 body2 ...)
       (gentemps (var1 ...) () ((var1 init1) ...) body1 body2 ...)))))