简体   繁体   中英

Common Lisp: Passing Symbol to Macro

The purpose of this macro is to create a macro that gives a name to accessing a certain key of an associated list.

(defmacro generate-accessor (key-symbol prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (string key-symbol))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

So when I try it -

CL-USER> (generate-accessor 'a "alist")
; ERROR> 'A cannot be coerced to a string.

and yet...

CL-USER> (string 'a)
; RESULT> "A"

So I try again using SYMBOL-NAME to coerce the symbol into a string

(defmacro generate-accessor (key-symbol prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (symbol-name key-symbol))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

This time when I try it -

CL-USER> (generate-accessor 'a "alist")
; ERROR> The value 'A is not of type SYMBOL.

and yet...

CL-USER> (symbol-name 'a)
; RESULT>"A"
CL-USER> (symbolp 'a)
; RESULT>T

Whenever I use 'a outside of my macro, it gets automatically interned as a symbol like I expect. Yet somehow when I pass 'a to my macro, it arrives as a quoted chunk. I don't understand why it isn't being evaluated, especially at a point before the backquote begins. I know I am not understanding something fundamental to Lisp but I don't know how to see it right now.

'a is shorthand for (quote a) , which is the list that you're passing to your macro. Macro arguments are not evaluated, but passed as is. When used as an argument to a function (ie, not to a macro) (quote a) is evaluated first, and the result of evaluating (quote a) is the symbol a . Consider, for instance, the difference between

(list 'a)           ===
(list (quote a)) 
; => (a)

and

'('a)               ===
'((quote a))        ===   
(quote ((quote a)))
; => ((quote a)) ;; which may also be printed ('a)

An example of using a symbol argument to a macro

Based on a request in the comments, here's a defstruct -like macro that creates some functions that incorporate the name of the structure.

(defmacro my-defstruct (name slot)
  "A very poor implementation of defstruct for structures
that have exactly one slot"
  (let ((struct-name (string name))
        (slot-name (string slot)))
    `(progn
       (defun ,(intern (concatenate 'string (string '#:make-) struct-name)) (value)
         (list value))
       (defun ,(intern (concatenate 'string (string struct-name) "-" slot-name)) (structure)
         (car structure)))))

Here's what, eg, (my-defstruct foo bar) expands to:

CL-USER> (pprint (macroexpand '(my-defstruct foo bar)))

(PROGN
 (DEFUN MAKE-FOO (VALUE) (LIST VALUE))
 (DEFUN FOO-BAR (STRUCTURE) (CAR STRUCTURE)))

Examples of use:

CL-USER> (my-defstruct foo bar)
FOO-BAR
CL-USER> (make-foo 34)
(34)
CL-USER> (foo-bar (make-foo 34))
34

Whenever I use 'a outside of my macro, it gets automatically interned as a symbol like I expect.

Why that. I expect that it is a quoted form and on evaluation I get the quoted object.

Remember: a macro transforms source code, not evaluated objects.

Since your code does not evaluate it and you call SYMBOL-NAME on the quoted form, I expect that to be an error.

You can debug the macro like other code. Print out what the macro gets as argument or just use the debugger to look at the backtrace and see the various variable values.

The macro CHECK-TYPE checks the type of some place.

CL-USER 15 > (defmacro generate-accessor (key-symbol prefix)
               (check-type key-symbol symbol)
               (check-type prefix string)
               (let ((mac-name 
                      (intern (string-upcase (concatenate 'string
                                                          prefix "-"
                                                          (string key-symbol))))))
                 `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))
GENERATE-ACCESSOR

CL-USER 16 > (generate-accessor 'a "alist")

Error: The value (QUOTE A) of KEY-SYMBOL is not of type SYMBOL.
  1 (continue) Supply a new value of KEY-SYMBOL.
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

So the CHECK-TYPE complains that its not a symbol, but a list of two elements. Which is expected, since the macro works on source code, not evaluated objects.

Btw., writing the accessor as a macro is already wrong. Common Lisp has functions - use them.

Apparently you didn't notice that 'a wasn't evaluated when your macro was called:

(defmacro generate-accessor (**key-symbol** prefix)
  (let ((mac-name 
          (intern (string-upcase (concatenate 'string
                                              prefix "-"
                                              (string **key-symbol**))))))
    `(defmacro ,mac-name (alis) `(assoc ,',key-symbol ,alis))))

key-symbol is just substituted rather than evaluated. For example, define a simpler macro as follows:

(defmacro foo (a b)                                                                                          
  (let ((x a) (y b) (z (concatenate 'string (string a) b)))                                                  
    `(print (list ,a ,b ,x ,y ,z))))

Pass in 'a and "b":

CL-USER> (foo 'a "b")                                                                                        
; Evaluation aborted on #<SIMPLE-TYPE-ERROR expected-type: SB-KERNEL:STRING-DESIGNATOR datum: 'A>.

After execute (setq a 1) , pass in a and "b":

CL-USER> (foo a "b")                                                               
(1 "b" 1 "b" "Ab")                                                                                           
(1 "b" 1 "b" "Ab")

You can figure out that a (without quotation mark) is just literally substituted into the let clause since there is no eval comma in it.

To fix that, you can pass in a symbol without quotation mark, or rewrite your macro into a function like this:

(defun generate-accessor (key-symbol prefix)
  (eval
    (let ((mac-name 
             (intern (string-upcase (concatenate 'string
                                                 prefix "-"
                                                 (string key-symbol))))))
      `(defmacro ,mac-name (alis) `(assoc ',key-symbol ,alis)))))

But this is not the final answer -- how would you evaluate key-symbol while keeping alis untouched? I think this is impossible.

Maybe you could change the inner defmacro into defun as well:

(defun generate-accessor (key-symbol prefix)
  (eval
    (let ((mac-name 
           (intern (string-upcase (concatenate 'string
                                               prefix "-"
                                               (string key-symbol))))))
      `(defun ,mac-name (alis) (assoc ',key-symbol alis)))))

It works!

CL-USER> (generate-accessor 'a "x")                                                              
X-A

Macro is powerful, but don't overuse!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM