Macros and how to trace them

2020-04-18 05:50发布

问题:

The trace macro is very useful for debugging. But it comes to a halt, when used upon any macro. Like if I try to do the following :

CL-USER> (trace push)

Then, it'll give an error saying:

can't use encapsulation to trace anonymous function #<FUNCTION (MACRO-FUNCTION
                                                                PUSH) {100053FB9B}>
   [Condition of type SIMPLE-ERROR]

Well, that's obvious because the clhs page of trace, clearly defines it upon functions. So, what is the reason for not having any facility for tracing macros in Common Lisp?
Is there any other (unconventional) way to trace macros in Common Lisp?

回答1:

The Common Lisp standard only mentions tracing of functions. In compiled implementations, macro expansion usually takes place at compile time and thus tracing of macros is usually not supported.

But some Common Lisp implementations can trace macros via a Lisp interpreter (!):

CLISP can trace macros:

[1]> (defmacro foo (a) a)
FOO
[2]> (trace foo)
;; Tracing macro FOO.
(FOO)
[3]> (loop for i below 4 collect (foo i))
1. Trace: (FOO I)
1. Trace: FOO ==> I
1. Trace: (FOO I)
1. Trace: FOO ==> I
1. Trace: (FOO I)
1. Trace: FOO ==> I
1. Trace: (FOO I)
1. Trace: FOO ==> I
(0 1 2 3)

LispWorks is another implementation which supports tracing of macros.

So, what is the reason for not having any facility for tracing macros in Common Lisp?

As mentioned that's the language standard. Beyond the language standard implementations provide all kinds of language extensions in various ways, including the ability of some Lisp interpreters (!) to trace macros.

If the code is already compiled, tracing won't work anyway. Having a Lisp interpreter helps, but implementations are not required to have an interpreter. Lisp interpreter here means an executing engine which works from Lisp code as data.



回答2:

Using trace on a macro seems a little odd, but it works in CLISP:

(trace push)
(defparameter *stack* '())

(defun push-xy (x y)
  (push x *stack*)
  (push y *stack*))
; 1. Trace: (push x *stack*)
; 1. Trace: push ==> (setq *stack* (cons x *stack*))
; 1. Trace: (push y *stack*)
; 1. Trace: push ==> (setq *stack* (cons y *stack*))
; ==> push-xy

The standard does not say when it should expand macros so this might happen when functions and lambdas are defined, compiled and sometimes called. Some implementations run the macro twice so you get double the output.

I never use this. I rather use macroexpand-1:

(macroexpand-1 '(push x *stack)))
; ==> (setq *stack (cons x *stack))
; ==> t

If your form returns a new form that uses macros you might want to try macroexpand instead. Its like calling macroexpand-1 over and over until there are no transformation left.



回答3:

*macroexpand-hook* is the expected method for tracing macros.

http://www.lispworks.com/documentation/HyperSpec/Body/v_mexp_h.htm#STmacroexpand-hookST