0

I am using SBCL, Slime, and Emacs to develop in Common Lisp.

I have this function:

(defun build-cond-action-pairs (&rest var) (labels ((aux (xs-left accu) (cond ((null (cddr xs-left)) (append accu (list (list (first xs-left) (second xs-left))))) (t (aux (cddr xs-left) (append accu (list (list (first xs-left) (second xs-left))))))))) (aux var nil))) 

I also defined these two variables:

CL-USER>(defparameter var-a 1) VAR-A CL-USER> (defparameter var-b 1) VAR-B 

When I call the function with:

CL-USER> (build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b)) 

As expected, the arguments are evaluated:

(("fish are cool" 2) ("amphibians are cool" 2)) 

I want to transform this function into a macro. Hence, the arguments will not be evaluated.

The desired output result would be:

(("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b))) 

I tried with:

 CL-USER> (defmacro macro-build-cond-action-pairs (&rest var) `(labels ((aux (,xs-left ,accu) (cond ((null (cddr ,xs-left)) (append ,accu (list (list (first ,xs-left) (second ,xs-left))))) (t (aux (cddr ,xs-left) (append ,accu (list (list (first ,xs-left) (second ,xs-left))))))))) (aux ,var nil))) 

But, it does not work:

; in: DEFMACRO MACRO-BUILD-COND-ACTION-PAIRS ; `(LABELS ((AUX (,XS-LEFT ,ACCU) ; (COND (# #) (T #)))) ; (AUX ,VAR NIL)) ; --> SB-IMPL::|List| SB-IMPL::|List| SB-IMPL::|List| ; ==> ; (SB-IMPL::|List| XS-LEFT ACCU) ; ; caught WARNING: ; undefined variable: COMMON-LISP-USER::ACCU ; ; caught WARNING: ; undefined variable: COMMON-LISP-USER::XS-LEFT ; ; compilation unit finished ; Undefined variables: ; ACCU XS-LEFT ; caught 2 WARNING conditions MACRO-BUILD-COND-ACTION-PAIRS 

Feels like a package (or namespace problem). Maybe the root is the labels part. I do not know how to solve it.

How can I fix this?

Thanks.

10
  • 2
    what output you want the macro to return? Commented Apr 6, 2022 at 4:12
  • 3
    WHY do you want it to be a macro?! Commented Apr 6, 2022 at 4:19
  • 1
    You need to macro-expand the macro - it will then show you what the macro actually does. Commented Apr 6, 2022 at 7:08
  • 1
    A macro is something that transforms source code in an extended Lisp into source code in a more restricted Lisp: what source code do you want to start with, and what source code do you want to end up with? That will tell you both if you need a macro at all and how to write it. Without knowing that it's not really possible to answer this question. Commented Apr 6, 2022 at 7:39
  • 1
    If you quote the arguments, they won't be evaluated: '(incf var-a) or (quote (incf var-a)) (but what do you want to do etc). Commented Apr 6, 2022 at 9:52

2 Answers 2

1
;; from: ;; (build-cond-action-pairs "fish are cool" (incf var-a) ;; "amphibians are cool" (incf var-b)) ;; the macro-expansion should be: ;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b))) ;; of course, this is makeable - with common lisp. 

However, macro's are then executed - and execution of this is not possible because the first position of this list ("fish are cool" (incf var-a)) doesn't return a function.

But if the macro-expansion is

;; (list '("fish are cool" (incf var-a)) '("amphibians are cool" (incf var-b))) ;; which is equivalent to: ;; (list (quote ("fish are cool" (incf var-a))) (quote ("amphibians are cool" (incf var-b)))) ;; it would evaluate to: ;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b))) 

This would be regular lisp.

Although it does not expand to a valid lisp expression, we can achieve it even that it expands really to:

(("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b))) 

Because using macroexpand-1 you can check to what the macro expands to - no matter whether the evaluation of the expanded expression will give an error or not. So I will show you both possibilities.

;; we want ;; (macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) ;; "amphibians are cool" (incf var-b))) ;; returns: ;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b))) (defmacro build-cond-action-pairs (&rest var) ...) ;; so we want this macro loops over its var elements and pairs the elements. ;; we need a function which takes a list and generates a list of lists ;; where the inner lists group pairs of elements. ;; One can achieve this with loop. (defun to-pairs (l) "Group elements of list l in lists of length 2 - pairs." (loop for (a b &rest x) on l by #'cddr collect (list a b))) ;; a macro takes its arguments list and doesn't evaluate its arguments. ;; we can use inside macros such functions to re-arrange the arguments list ;; - we can use list/data manipulation functions to re-arrange code - this ;; is the power of macros in common lisp! (defmacro build-cond-action-pairs (&rest var) `,(to-pairs `,var)) ;; try it out: (macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b))) ;; returning: (("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B))) ; T 

So this expands exactly to how you want it. But when executing the macro there will be an error:

(build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b)) *** - EVAL: ("fish are cool" (INCF VAR-A)) is not a function name; try using a symbol instead The following restarts are available: USE-VALUE :R1 Input a value to be used instead. ABORT :R2 Abort main loop 

(I tried it out in the implementation clisp of common lisp - just because I use it for very quick tests often - while for serious programming I use emacs + sbcl).

I wanted just to demonstrate you that lisp can do even this.

So let's build the other variant with list and quote:

(defun to-quoted-pairs (l) "Group elements of list l in quoted lists of length 2 - quoted pairs." (loop for (a b &rest x) on l by #'cddr collect (list 'quote (list a b)))) (defmacro build-cond-action-pairs (&rest var) `,(to-quoted-pairs `,var)) (macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b))) ;;=> ('("fish are cool" (INCF VAR-A)) '("amphibians are cool" (INCF VAR-B))) ;;=> T 

This is nearly what we want - we just want to cons a #'list at the start. So:

(defmacro build-cond-action-pairs (&rest var) (cons 'list `,(to-quoted-pairs `,var))) (macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b))) ;;=> (LIST '("fish are cool" (INCF VAR-A)) ;;=> '("amphibians are cool" (INCF VAR-B))) ; ;;=> T ;; That's it! and we can run it without error: (build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b)) ;;=> (("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B))) 

Voila! We made it!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! I also managed to come up with an answer. Check it out.
0

@Gwang-JinKim presented a solution (thanks for the help!). However, his solution changes the recursive approach described in my original answer.

I ended up finding a way to fix the macro keeping it very similar to the original question. Basically, it was necessary to remove some commas and to insert a (quote ...) before the tail call.

Check it out:

CL-USER> (defmacro macro-build-cond-action-pairs (&rest var) `(labels ((aux (xs-left accu) (cond ((null (cddr xs-left)) (append accu (list (list (first xs-left) (second xs-left))))) (t (aux (cddr xs-left) (append accu (list (list (first xs-left) (second xs-left))))))))) (aux (quote ,var) nil))) 

It works:

CL-USER> (macro-build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b)) (("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B))) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.