Wei Li
Wei Li

Reputation: 471

How to pass a list to macro in common lisp?

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

Answers (3)

Michael He
Michael He

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

acelent
acelent

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

finnw
finnw

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

Related Questions