Common Lisp macro recursive extension

I once played with macros and came up with this:

(defmacro my-recursive-fact (n) (if (= 0 n) '1 (let ((m (1- n))) `(* ,n (my-recursive-fact ,m))))) 

And it worked.

 CL-USER> (my-recursive-fact 5) 120 

So, I thought this was a good way to show students an example of recursion if I parse this macro with macroexpand :

 CL-USER> (macroexpand '(my-recursive-fact 5)) (* 5 (MY-RECURSIVE-FACT 4)) T 

That is, there is no difference between macroexpand-1 and macroexpand in this case. I'm sure I miss a key point in understanding macroexpand , and HyperSpec says nothing special about recursive macros.

And also I'm still curious to know if there is a way to extend such a macro to the end.

+9
source share
4 answers

MACROEXPAND takes shape and expands it. He does this several times until the form is no longer a macro form.

In your example, the top-level call my-recursive-fact is a macro. The result form with front multiplication is not a macroscopic form, since * not a macro. This is a function. A form has an argument, which is a macro form. But MACROEXPAND does not look at them.

If you want to deploy code at all levels, you need to use a code walker. Some Lisps have it in an IDE available directly, such as Lispworks.

+8
source

Sliz has a slime-macroexpand-all command for code walking: http://common-lisp.net/project/slime/doc/html/Macro_002dexpansion.html

This may be undocumented and / or unsupported, but maybe you can call it from REPL:

 CL-USER> (swank-backend:macroexpand-all '(my-recursive-fact 5)) (* 5 (* 4 (* 3 (* 2 (* 1 1))))) 
+10
source

You can also use sb-cltl2:macroexpand-all

 CL-USER> (sb-cltl2:macroexpand-all '(my-recursive-fact 5)) (* 5 (* 4 (* 3 (* 2 (* 1 1))))) 
+2
source

Edit: this will break if there is a variable in the operatorโ€™s special design whose name matches the macro name in the carโ€™s position of the form. For example: (let ((setf 10)) (print setf))


This is pretty old, but if someone who came across this question wished there was some kind of portable way to recursively expand macros:

 (defun recursive-macroexpand (form) (let ((expansion (macroexpand form))) (if (and (listp expansion) (not (null expansion))) (cons (car expansion) (mapcar #'recursive-macroexpand (cdr expansion))) expansion))) 

E.G. (tested in SBCL and CLISP):

 (recursive-macroexpand '(my-recursive-fact 5)))) => (* 5 (* 4 (* 3 (* 2 (* 1 1))))) 

A more ugly example (a regular macroexpand would leave the second dolist unchanged):

 (recursive-macroexpand '(dolist (x '(0 1)) (dolist (y '(0 1)) (format t "decimal: ~a binary: ~a~a~%" (+ (* x 2) (* y 1)) xy)))) => (block nil (let* ((#:list-8386 '(0 1)) (x nil)) nil (tagbody #:loop-8387 (if (endp #:list-8386) (go #:end-8388)) (setq x (car #:list-8386)) (block nil (let* ((#:list-8389 '(0 1)) (y nil)) nil (tagbody #:loop-8390 (if (endp #:list-8389) (go #:end-8391)) (setq y (car #:list-8389)) (format t "decimal: ~a binary: ~a~a~%" (+ (* x 2) (* y 1)) xy) (setq #:list-8389 (cdr #:list-8389)) (go #:loop-8390) #:end-8391 (return-from nil (progn nil))))) (setq #:list-8386 (cdr #:list-8386)) (go #:loop-8387) #:end-8388 (return-from nil (progn nil))))) 
+1
source

Source: https://habr.com/ru/post/958760/


All Articles