[racket] Macros baffle me

From: Marco Maggi (marco.maggi-ipsu at poste.it)
Date: Fri May 2 03:25:44 EDT 2014

"Eduardo Costa" wrote:
> In general,  I don't have  problems with macros  in Common Lisp  or in
> Scheme. However, macros in Racket baffle me.

I assume you  mean that you are fine with  DEFINE-MACRO; hygienic macros
are significantly different.  You appear to grasp basic pattern matching
and output form composition by SYNTAX-RULES, good.

> I  simply  cannot  figure  out  how  syntax->datum,  syntax->list  and
> datum->syntax work.

For now, that's all right for SYNTAX->DATUM and DATUM->SYNTAX; getting a
basic understanding of SYNTAX->LIST was very important for me when I was
struggling   to  understand   hygienic  macros.    Here  some   function
implementations to look at:

   (define (syntax-car stx)
     (syntax-case stx ()
       ((?car . ?cdr)
        (syntax ?car))
       ))

   (define (syntax-cdr stx)
     (syntax-case stx ()
       ((?car . ?cdr)
        (syntax ?cdr))
       ))

   (define (syntax->list stx)
     (syntax-case stx ()
       (()
        '())
       ((?car . ?cdr)
        (cons (syntax ?car)
              (syntax->list (syntax ?cdr))))
       ))

   (syntax-car (syntax (a . b))) => #<syntax expr=a>
   (syntax-cdr (syntax (a . b))) => #<syntax expr=b>
   (syntax->list (syntax (a b c)))
   => (#<syntax expr=a> #<syntax expr=b> #<syntax expr=c>)

  I will  leave to other  people to suggest tutorials  and documentation
that are considered  good these days; rather I include  working code for
you to  study; I  think it  does what I  understand you  want to  do.  I
assume in good faith that this is not homework.

  A couple of notes first:

* Understand that the notation:

     #'?thing

  is equivalent to:

     (syntax ?thing)

* Take note that:

     (syntax-case stx ()
       ((?car . ?cdr)
        (free-identifier=? #'?car #'and)
        (do-something)))

  is equivalent to the shorter:

     (syntax-case stx (and)
       ((and . ?cdr)
        (do-something)))

  because  the code  SYNTAX-CASE  expands to  contains an  automatically
  built:

     (free-identifier=? #'?car #'and)

  Now the code.  I am no Racket user (sorry), this is standard R6RS code
without Racket specific bindings[1].  Most likely, Racket has better and
built-in functions you can use in place of DELETE-DUPLICATE-IDENTIFIERS,
and maybe also FREE-VARS.

[1] <http://docs.racket-lang.org/r6rs/Using_R6RS_with_DrRacket.html?q=r6rs>

;; demo.sps --

#!r6rs
(import (rnrs (6)))

(define-syntax with-vars
  (syntax-rules ()
    ((_ (?id0) ?body0 ?body ...)
     (for-all (lambda (?id0) ?body0 ?body ...)
       '(#t #f)))
    ((_ (?id0 ?id ...) ?body0 ?body ...)
     (for-all (lambda (?id0)
                (with-vars (?id ...) ?body0 ?body ...))
       '(#t #f)))
    ))

(define-syntax tauta
  (lambda (stx)
    (define free-vars
      (case-lambda
       ((stx)
        (free-vars stx '()))
       ((stx id*)
        ;;Collect the free variables appearing  in the syntax object STX
        ;;and  accumulate them  in  the list  of  identifiers ID*;  this
        ;;function is a recursive code walker inside STX.  For instance:
        ;;
        ;;   (free-vars #'(or Q (or (not P) P)))
        ;;   => (#'P #'Q)
        ;;
        ;;In  the context  of this  function: a  "free variable"  is any
        ;;identifier not FREE-IDENTIFIER=? to one among:
        ;;
        ;;  and or then -> <-> not equiv display displayln
        ;;
        ;;We assume STX represents a valid code form.
        ;;
        ;;NOTE We do honor QUOTE but not QUASIQUOTE, SYNTAX, QUASISYNTAX
        ;;etc.
        (syntax-case stx (and or then -> <-> not equiv display displayln
                              quote)
          ((and . ?form)
           (free-vars #'?form id*))
          ((or . ?form)
           (free-vars #'?form id*))
          ((then . ?form)
           (free-vars #'?form id*))
          ((-> . ?form)
           (free-vars #'?form id*))
          ((<-> . ?form)
           (free-vars #'?form id*))
          ((not . ?form)
           (free-vars #'?form id*))
          ((equiv . ?form)
           (free-vars #'?form id*))
          ((display . ?form)
           (free-vars #'?form id*))
          ((displayln . ?form)
           (free-vars #'?form id*))

          ((quote . ?datum)
           ;;Do not visit the datum.
           id*)

          ((?id . ?form)
           (identifier? #'?id)
           (free-vars #'?form (cons #'?id id*)))
          ((?car . ?cdr)
           (append (free-vars #'?car)
                   (free-vars #'?cdr)
                   id*))
          (?atom
           id*)
          ))
       ))

    (define delete-duplicate-identifiers
      (case-lambda
       ((id*)
        (delete-duplicate-identifiers id* free-identifier=?))
       ((id* identifier=)
        ;;Given  the  list  of  identifiers  ID*  remove  the  duplicate
        ;;identifiers and return a proper list of unique identifiers.
        ;;
        (assert (and (list? id*) (for-all identifier? id*)))
        (let clean-tail ((id* id*))
          (if (null? id*)
              '()
            (let ((head (car id*)))
              (cons head (clean-tail (remp (lambda (id)
                                             (identifier= id head))
                                       (cdr id*))))))))))

    (syntax-case stx ()
      ((_ ?expr)
       (let* ((var* (free-vars #'?expr))
              (var* (delete-duplicate-identifiers var*)))
         #`(with-vars #,var* ?expr)))
      )))

(define-syntax print-tauta
  (syntax-rules ()
    ((_ ?expr)
     (let ((input-expr (quote ?expr))
           (result     (tauta ?expr))
           (port       (current-output-port)))
       (display input-expr port)
       (display " => "     port)
       (display result     port)
       (newline port)
       (flush-output-port port)))
    ))

(define (then x y)
  (or y (not x)))

(define (equiv x y)
  (and (then x y) (then y x)))

(print-tauta (or (then P Q) (then Q (not P))))
(print-tauta (or P (not P))) ; excluded middle
(print-tauta (or (and P Q) (or (not P) (not Q))))
(print-tauta (equiv (then P Q) (then (not Q) (not P)))) ;equiv
(print-tauta (then (and (then (not A) B) (then (not A) (not B))) A)) ;reductio ad absurdum
(print-tauta (equiv (not (and A B)) (or (not A) (not B)))) ; De Morgan's law

;;; end of file
-- 
"Now feel the funk blast!"
Rage Against the Machine - "Calm like a bomb"

Posted on the users mailing list.