0
votes

I'm in a process of implementing Hygienic macros in my Scheme implementation, I've just implemented syntax-rules, but I have this code:

(define odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

what should be the difference between that and this:

(define-syntax odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

from what I understand syntax-rules just return syntax transformer, why you can't just use define to assign that to symbol? Why I need to use define-syntax? What extra stuff that expression do?

Should first also work in scheme? Or only the second one?

Also what is the difference between let vs let-syntax and letrec vs letrec-syntax. Should (define|let|letrec)-syntax just typecheck if the value is syntax transformer?

EDIT:

I have this implementation, still using lisp macros:

;; -----------------------------------------------------------------------------
(define-macro (let-syntax vars . body)
  `(let ,vars
     ,@(map (lambda (rule)
              `(typecheck "let-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (letrec-syntax vars . body)
  `(letrec ,vars
     ,@(map (lambda (rule)
              `(typecheck "letrec-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (define-syntax name expr)
  (let ((expr-name (gensym)))
    `(define ,name
       (let ((,expr-name ,expr))
         (typecheck "define-syntax" ,expr-name "syntax")
         ,expr-name))))

This this code correct?

Should this code works?

(let ((let (lambda (x) x)))
  (let-syntax ((odd? (syntax-rules ()
                        ((_ x) (not (even? x))))))
     (odd? 11)))
1

1 Answers

2
votes

This question seems to imply some deep confusion about macros.

Let's imagine a language where syntax-rules returns some syntax transformer function (I am not sure this has to be true in RnRS Scheme, it is true in Racket I think), and where let and let-syntax were the same.

So let's write this function:

(define (f v)
  (let ([g v])
    (g e (i 10)
       (if (= i 0)
           i
           (e (- i 1))))))

Which we can turn into this, of course:

(define (f v n)
  (v e (i n)
     (if (<= i 0)
         i
         (e (- i 1)))))

And I will tell you in addition that there is no binding for e or i in the environment.

What is the interpreter meant to do with this definition? Could it compile it? Could it safely infer that i can't possibly make any sense since it is used as a function and then as a number? Can it safely do anything at all?

The answer is that no, it can't. Until it knows what the argument to the function is it can't do anything. And this means that each time f is called it has to make that decision again. In particular, v might be:

(syntax-rules ()
  [(_ name (var init) form ...)
   (letrec ([name (λ (var)
                    form ...)])
     (name init))]))

Under which the definition of f does make some kind of sense.

And things get worse: much worse. How about this?

(define (f v1 v2 n)
  (let ([v v1])
    (v e (i n)
       ...
       (set! v (if (eq? v v1) v2 v1))
       ...)))

What this means is that a system like this wouldn't know what the code it was meant to interpret meant until, the moment it was interpreting it, or even after that point, as you can see from the second function above.

So instead of this horror, Lisps do something sane: they divide the process of evaluating bits of code into phases where each phase happens, conceptually, before the next one.

Here's a sequence for some imagined Lisp (this is kind of close to what CL does, since most of my knowledge is of that, but it is not intended to represent any particular system):

  1. there's a phase where the code is turned from some sequence of characters to some object, possibly with the assistance of user-defined code;
  2. there's a phase where that object is rewritten into some other object by user- and system-defined code (macros) – the result of this phase is something which is expressed in terms of functions and some small number of primitive special things, traditionally called 'special forms' which are known to the processes of stage 3 and 4;
  3. there may be a phase where the object from phase 2 is compiled, and that phase may involve another set of user-defined macros (compiler macros);
  4. there is a phase where the resulting code is evaluated.

And for each unit of code these phases happen in order, each phase completes before the next one begins.

This means that each phase in which the user can intervene needs its own set of defining and binding forms: it needs to be possible to say that 'this thing controls what happens at phase 2' for instance.

That's what define-syntax, let-syntax &c do: they say that 'these bindings and definitions control what happens at phase 2'. You can't, for instance, use define or let to do that, because at phase 2, these operations don't yet have meaning: they gain meaning (possibly by themselves being macros which expand to some primitive thing) only at phase 3. At phase 2 they are just bits of syntax which the macro is ingesting and spitting out.