Reputation: 471
I'm trying to pass a list to macro, for example:
(defmacro print-lst (lst)
`(progn
,@(mapcar #'(lambda (x) `(print ,x)) lst)))
(let ((lst '(1 2 3)))
(print-lst lst))
It caught error: "The value LST is not of type LST".
So, my question is, what's wrong with this piece of code and how to pass list to macro?
Upvotes: 3
Views: 2613
Reputation: 41
Your code unnecessarily used macro. Actually, you can just eval (mapcar #'print '(1 2 3)) as someone mentioned above. You can also use (print-lst (1 2 3)) without the list quotation mark, but it's not recommended since that doesn't fit the general practice to call a function with arguments.
All in all, when calling a macro, you shouldn't cite a symbol without an eval comma ',' if you expect it be evaluated, because it will be literally substituted into the macro template.
e.g.
(setq a 1)
;;A won't be evaluated without the comma
(defmacro foo (x) `(progn ,(print x))
(foo 'a) ;;=> 'A (A is returned since (print 'a) is evaluated)
(foo a) ;;=> A (1 is returned since (print a) is evaluated)
;;A is evaluated
(defmacro bar (x) `(print ,x))
(bar 'a) ;;=> A
(bar a) ;;=> 1
Your code:
(defmacro print-lst (lst)
`(progn
,@(mapcar #'(lambda (x) `(print ,x)) lst)))
The 'mapcar' form will be evaluated as a whole, but 'lst' will only be substituted for what you pass in.
I can define a function instead of a macro to fix the problem:
(defun print-lst (lst)
(mapcar #'eval
(mapcar #'(lambda (x) `(print ,x)) lst)))
Then:
(let ((lst '(1 2 3)))
(print-lst lst))
;;=>
1
2
3
(1 2 3)
If you got interested in the question as you titled, you can see my another answer about this:
How do I apply "or" to a list in elisp
Upvotes: 1
Reputation: 8135
What you're attempting to do with your macro is to expand a literal list.
Macro arguments are not evaluated. So, print-lst
is actually receiving the symbol lst
, not the list bound to the variable.
You either aknowledge that and give print-lst
a literal list, or you may generate code that evaluates the macro argument:
(defmacro print-lst (lst)
(let ((item (gensym)))
;; Macros usually make sure that expanded arguments are
;; evaluated only once and in left-to-right order.
;;
;; In this case, we only have one argument and we only evaluate it once.
`(dolist (,item ,lst)
(print ,item))))
Although, this is obviously not a good example of a macro, it would much better be a function:
(defun print-lst (lst)
(dolist (item lst)
(print item)))
If you wish to inline calls to print-lst
, you may consult your implementation's documentation to see if it pays attention to (declaim (inline print-lst))
.
Another option is to use a compiler macro, in complement to the function, to inline calls where the evaluation of the argument is a known value at compile-time, but again see if your implementation pays any attention to compiler macros:
(define-compiler-macro print-lst (&whole form lst &environment env)
(declare (ignorable env))
;; Some implementations have an eval function that takes an environment.
;; Since that's not standard Common Lisp, we don't use it in constantp.
(if (constantp lst)
`(progn
,@(mapcar #'(lambda (item)
`(print ,item))
(eval lst)))
;; Return the original form to state you didn't transform code.
form))
Upvotes: 4
Reputation: 48659
I'm not sure why you want to define this as a macro instead of a regular function, but the problem is that macros do not evaluate their arguments. If you give it the name of a lexical variable, all it sees is the name ('LST
), not the bound value. It is complaining (correctly) that the symbol 'LST
is not a list, and thus not a valid second argument to MAPCAR
.
You could call it as (print-lst (1 2 3))
, but then you could do without the macro and just do (mapc #'print lst)
Upvotes: 8