Use a variable from the external (lexical) environment in a macro

How do I get this piece of macro to function as intended? - I would like to grab p from the lexical environment without sending it to the macro as an argument.

(define-syntax-rule (fi a b)
    (if p a b)) ;--->capture `p` from lexical env

(let ((p #t))
    (fi 1 2))

      

Bonus thanks - How would I do the same in CL?

+3


source to share


2 answers


In Common Lisp, a macro is simply a function that takes a list structure of code as input and returns a list structure representing the new code.

(defmacro fi (a b)
  `(if p ,a ,b))

      

So, if you have to use fi like this:

(let ((p t)) ; Common Lisp uses 't' for truth.
   (fi 1 2))

      

It is as if you had typed:

(let ((p t))
  (if p 1 2))

      



To see how you get this extension, let's say the fi function was a function and you gave it arguments 1 and 2.

(fi 1 2) => (if p 1 2)

      

Then I returned the list structure it returned and replaced it with a call to fi.

The example you give is simple because the arguments are evaluated by themselves. If you had something more complex like expressions (* 1 1) and (+ 1 1), the actual structure of the list is passed (the value of a is a list (* 1 1) and the value of b is a list (+ 1 1) )

(fi (* 1 1) (+ 1 1)) => (if p (* 1 1) (+ 1 1))

      

+2


source


You cannot commit local bindings with syntax-rules

. You can use syntax-case

for this:

(define-syntax fi
  (lambda (stx)
    (syntax-case stx ()
      ((_ a b)
       (with-syntax ((p (datum->syntax stx #'p)))
         #'(if p a b))))))

      

However, using a datum->syntax

fixed name like this to capture identifiers is not ideal. If you are using Racket it is better to use syntax parameters for this.




For schema implementations that do not have syntax-case

but have an explicit rename, you can write a macro like this:

(define-syntax fi
  (er-macro-transformer
    (lambda (exp rename compare)
      `(,(rename 'if) p ,(cadr exp) ,(caddr exp)))))

      

Some people find this more straightforward, but the burden of responsibility is that you rename anything you don't intentionally capture. In this case, we are explicitly renaming if

; for most of the other macros that use lambda

, let

etc., they all need to be renamed.

+1


source







All Articles