Can the inner workings of a Common Lisp macro be made evident with more than macroexpand(-1)? - Stack Overflow

I have this macro:(defmacro if (test-form &body body)(let ((test (gensym)))`(let ((,test (cl:if (b

I have this macro:

(defmacro if (test-form &body body)
  (let ((test (gensym)))
    `(let ((,test (cl:if (booleanp ,test-form)
                         (cl-truth ,test-form)
                         ,test-form)))
       (cl:if ,test
              ,@body))))

Using it results in this:

LO> (if (wordp (wordify ""))
        (print 1)
        (print 0))
NIL

(You can see, I work in a package lo that makes:use of common-lisp but cl:if is in the :shadow. -- The function wordify uses make-word to create an instance of the coresponding defstruct. The predicate booleanp checks, whether a predicate's output is => TRUE, t resp. => FALSE, nil. cl-truth returns the second value then.)

macroexpand-1 is not useful here. It returns => NIL, NIL

If I try to consider the possible output, I appreciate destructuring-bind as a helpful tool:

(destructuring-bind (test-form &body body)
    '((wordp (wordify "")) (print 1) (print 0))
  (let ((test (gensym)))
    `(let ((,test (cl:if (booleanp ,test-form)
                         (cl-truth ,test-form)
                         ,test-form)))
       (cl:if ,test
              ,@body))))

The output is:

(LET ((#:G698
       (COMMON-LISP:IF (BOOLEANP (WORDP (WORDIFY "")))
                       (CL-TRUTH
                         (WORDP (WORDIFY "")))
                       (WORDP (WORDIFY "")))))
  (COMMON-LISP:IF #:G698
                  (PRINT
                    1)
                  (PRINT
                    0)))

And the result is the desired:

1
; No value

(It's a custom print that uses princ with no return value.)

Can you tell me, why the macro behaves different?

Thank you so very much.

EDIT:

Just to add a maybe more appropriate version of the macro, but without the strange behaviour solved.

(defmacro if-c (test-form instructionlist1 &optional instructionlist2)
  (let ((test (gensym)))
    `(let (;; The general Logo testform will return => TRUE, T / FALSE, NIL
           (,test (destructuring-bind (logo &optional cl)
                      (multiple-value-list ,test-form)
                    (cl:if (booleanp logo)
                           cl
                           (error "if doesn't like ~a as input"
                                  logo)))))
       (cl:if ,test
              ,instructionlist1
              ,instructionlist2))))    

EDIT No. 2:

I fot to mention: Yes, the strange behaviour is solved. Trivially, the macro above of package LC was not expanded in package LO but the one below instead:

(defmacro if (test-form &body body)
  ())

It was a left-over in LO where I was going to define it, before I decided to move it to LC.

I have this macro:

(defmacro if (test-form &body body)
  (let ((test (gensym)))
    `(let ((,test (cl:if (booleanp ,test-form)
                         (cl-truth ,test-form)
                         ,test-form)))
       (cl:if ,test
              ,@body))))

Using it results in this:

LO> (if (wordp (wordify ""))
        (print 1)
        (print 0))
NIL

(You can see, I work in a package lo that makes:use of common-lisp but cl:if is in the :shadow. -- The function wordify uses make-word to create an instance of the coresponding defstruct. The predicate booleanp checks, whether a predicate's output is => TRUE, t resp. => FALSE, nil. cl-truth returns the second value then.)

macroexpand-1 is not useful here. It returns => NIL, NIL

If I try to consider the possible output, I appreciate destructuring-bind as a helpful tool:

(destructuring-bind (test-form &body body)
    '((wordp (wordify "")) (print 1) (print 0))
  (let ((test (gensym)))
    `(let ((,test (cl:if (booleanp ,test-form)
                         (cl-truth ,test-form)
                         ,test-form)))
       (cl:if ,test
              ,@body))))

The output is:

(LET ((#:G698
       (COMMON-LISP:IF (BOOLEANP (WORDP (WORDIFY "")))
                       (CL-TRUTH
                         (WORDP (WORDIFY "")))
                       (WORDP (WORDIFY "")))))
  (COMMON-LISP:IF #:G698
                  (PRINT
                    1)
                  (PRINT
                    0)))

And the result is the desired:

1
; No value

(It's a custom print that uses princ with no return value.)

Can you tell me, why the macro behaves different?

Thank you so very much.

EDIT:

Just to add a maybe more appropriate version of the macro, but without the strange behaviour solved.

(defmacro if-c (test-form instructionlist1 &optional instructionlist2)
  (let ((test (gensym)))
    `(let (;; The general Logo testform will return => TRUE, T / FALSE, NIL
           (,test (destructuring-bind (logo &optional cl)
                      (multiple-value-list ,test-form)
                    (cl:if (booleanp logo)
                           cl
                           (error "if doesn't like ~a as input"
                                  logo)))))
       (cl:if ,test
              ,instructionlist1
              ,instructionlist2))))    

EDIT No. 2:

I fot to mention: Yes, the strange behaviour is solved. Trivially, the macro above of package LC was not expanded in package LO but the one below instead:

(defmacro if (test-form &body body)
  ())

It was a left-over in LO where I was going to define it, before I decided to move it to LC.

Share Improve this question edited Mar 25 at 5:48 Demihm Seinname asked Mar 23 at 20:57 Demihm SeinnameDemihm Seinname 3391 silver badge8 bronze badges 8
  • 1 MACROEXPAND works as expected for me. – Barmar Commented Mar 23 at 21:23
  • It produces the same output as your destructuring-bind test. – Barmar Commented Mar 23 at 21:24
  • BTW, your macro evaluates the condition twice -- once when calling booleanp, then again to get the value that's assigned to the gensym. You should bind the gensym to the value of the condition. Then call booleanp on that. – Barmar Commented Mar 23 at 21:26
  • Ah thank you. You're right. - Strange that macroexpand behaves so odd. Actually, it does not return NIL, NIL but NIL, T instead. but anyway. So, it must be something with my package definition. Now, I tried the example in the package LC where I defined the macro, and yes, it works. Thus, the trouble is in the package LO, where I am using it. – Demihm Seinname Commented Mar 23 at 22:34
  • I didn't do anything with packages, I just named my macro my-if so it wouldn't conflict. But it shouldn't matter, packages have no effect on macro expansion. – Barmar Commented Mar 24 at 0:27
 |  Show 3 more comments

1 Answer 1

Reset to default 3

MACROEXPAND-1 is actually sufficient to investigate macroexpansion in Common Lisp.

There is a possibility to use *MACROEXPAND-HOOK* to let things happen during macroexpansion.

I was thinking of TRACE for macros.

It is possible to print out the macroexpansions happening during a macro call. (And also count the depth of the expansions.)

(defun tracing-macroexpand-hook (expander form &optional env)
    (format t "[expanding] ~S~%" form)
    (let ((result (funcall expander form env)))
      (format t "=> ~S~%" result)
      result))

;; for convenience, let's define also a `with-macroexpansion-trace`
(defmacro with-macroexpansion-trace (&body body)
  `(let ((*macroexpand-hook* #'tracing-macroexpand-hook))
     ,@body))

Now, you can define some macros which are called in layers:

(defmacro m1 (x)
  `(m2 (+ ,x 1)))

(defmacro m2 (x)
  `(m3 (* ,x 2)))

(defmacro m3 (x)
  `(+ ,x 42))

And we can go:

(with-macroexpansion-trace
  (macroexpand '(m1 5)))

Resulting in the output:

[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
(+ (* (+ 5 1) 2) 42) ;
T

You can now also define a with-macroexpansion-result which applies with-macroexpansion-trace but at the end presents you also the evaluated result after the macroexpansion - and counts the total macro calls and returns the result:

(defmacro with-macroexpansion-result (form &key (evaluate nil))
  `(let ((*macroexpand-hook* #'tracing-macroexpand-hook))
     (let ((expanded (macroexpand ',form)))
       ,(if evaluate
            `(let ((result (eval expanded)))
               (format t "~&[result] ~S~%" result)
               result)
            'expanded))))

Let's try it:

(with-macroexpansion-result (m1 5) :evaluate t)

;; printing out:
[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
[result] 54
54

(defmacro with-macroexpansion-result (form &key (evaluate nil))
  `(let ((macro-call-count 0))
     (flet ((hook (expander f &optional env)
              (incf macro-call-count)
              (format t "~&[expanding] ~S~%" f)
              (let ((result (funcall expander f env)))
                (format t "=> ~S~%" result)
                result)))
       (let ((*macroexpand-hook* #'hook))
         (let ((expanded (macroexpand ',form)))
           (format t "~&[macro-calls-total] ~D~%" macro-call-count)
           ,(if evaluate
                `(let ((result (eval expanded)))
                   (format t "~&[result] ~S~%" result)
                   result)
                'expanded))))))
(with-macroexpansion-result (m1 5) :evaluate t)
[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
[macro-calls-total] 3
[result] 54
54

I guess that is the tool you wanted to have, isn't it? Some kind of macro-trace.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744269384a4566024.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信