简体   繁体   中英

Common Lisp Macros: correct expansion of a generated list

I am building a mechanism to take an arbitrary CLOS object and return a hash from it (useful in my debugging experience).

However, I am not sure how to force a variable expansion. I sense that the solution lies with a correct use of gensym, but I'm not sure how.

;;helper macro
(defun class-slots-symbols (class-name)
  "Returns a list of the symbols used in the class slots"
  (mapcar 'closer-mop:slot-definition-name
      (closer-mop:class-slots
       (find-class class-name))))

;;macro that I am having difficulty with
(defmacro obj-to-hash (obj-inst)
  "Reads an object, reflects over its slots, and returns a hash table of them"
  `(let ((new-hash (make-hash-table))
    (slot-list (class-slots-symbols (type-of ,obj-inst))))

    ;;The slot-list needs to expand out correctly in the with-slots form
    (with-slots (slot-list) obj-inst
       (loop for slot in slot-list do   ;and also here
        (format t "~a~&" slot)
        (hashset new-hash (string slot) slot)))))

After a macroexpand-1, I find that that this expands into the following code ( *bar* is a class object):

(macroexpand-1 '(obj-to-hash *bar*))

LET ((NEW-HASH (MAKE-HASH-TABLE))
      (SLOT-LIST (CLASS-SLOTS-SYMBOLS (TYPE-OF *BAR*))))
  (WITH-SLOTS (SLOT-LIST)  ;; <-- this needs to be expanded to *bar*'s slots
      *BAR*
    (LOOP FOR SLOT IN SLOT-LIST ;;<-- not so important
          DO (FORMAT T "~a~&" SLOT) (HASHSET NEW-HASH (STRING SLOT) SLOT))))

Obviously, the problem is that slot-list is not being expanded. Less obvious (to me) is the solution.


Followup: After Rainer pointed me in the right direction:

(defun class-slots-symbols (class-instance)
  "Returns a list of the symbols used in the class slots"
  (mapcar 'closer-mop:slot-definition-name
      (closer-mop:class-slots
       (class-of class-instance))))

(defun object-to-hash (obj)
  "Reflects over the slots of `obj`, and returns a hash table mapping
slots to their values"
  (let ((new-hash (make-hash-table))
    (slot-list (class-slots-symbols obj)))
    (loop for slot in slot-list do
     (hashset new-hash (string slot) 
          (slot-value  obj slot)))
    new-hash))

Just looking at it I can see no reason why this should be a macro. Rewriting it as a function will save you a lot of trouble.

The use of WITH-SLOTS is not possible they way you try it. The object is not known in general until runtime. The compiler needs to know the slots of the object at compile time already. You need to use SLOT-VALUE and look up the slot value at runtime.

You are thinking in many ways too complicated and your code is slightly confused. You can get rid of some confusion by following simple rules and avoiding some wording.

Let's look at your code :

First, it is not a helper macro, since what follows is a function.

;;helper macro
(defun class-slots-symbols (class-name)

Why take a class name? Why not use the class itself? Classes are first class objects. Write function with obvious interfaces. Elementary functions should work on the basic data types.

  "Returns a list of the symbols used in the class slots"

In the class slots no symbols are used. slots have names, one can get this symbol.

  (mapcar 'closer-mop:slot-definition-name
      (closer-mop:class-slots
       (find-class class-name))))

It is no wonder you have a problem with this macro. It is simply because it should be a function, not a macro. Macros are for source transformation. All you need is a simple computation, so no macro is needed

;;macro that I am having difficulty with
(defmacro obj-to-hash (obj-inst)

Poor wording: obj-inst. Either name it object or instance. Not both.

  "Reads an object, reflects over its slots, and returns a hash table of them"

Poor documentation: you don't READ anything. Read is an I/O operation and in your code is none. You are talking about an 'object', but above you have something like 'obj-inst'. Why talk about the same thing in two different ways? You may want to document what the hash table actual maps. From which keys to which values?

  `(let ((new-hash (make-hash-table))

new-hash is also a poor name. Basically the thing is a hash-table.

    (slot-list (class-slots-symbols (type-of ,obj-inst))))

Why TYPE-OF and then later in the helper function call FIND-CLASS? Common Lisp has CLASS-OF, which returns the class directly.

    ;;The slot-list needs to expand out correctly in the with-slots form
    (with-slots (slot-list) obj-inst

Above won't work since WITH-SLOTS expects slot names at compile time, not a slot-list.

       (loop for slot in slot-list do   ;and also here
        (format t "~a~&" slot)
        (hashset new-hash (string slot) slot)

HASHSET is not needed, unless it does something special. The usual way to set values is via SETF. SETF takes the form to read a place and the form to compute a value. That's all. It works for all kinds of data structures. One never needs to remember again how the writer function looks like (name, parameter list, ...).

))))

Here is my version :

Note that I use the package CLOS, you may want to use your package CLOSER-MOP

(defun class-slots-symbols (class)
  "Returns a list of the symbol names of the class slots"
  (mapcar 'clos:slot-definition-name
          (clos:class-slots class)))

Above is a simple function taking a class and returning the list of slot names.

Next, we have a simple function, which in this form has been written a million times in Common Lisp:

(defun object-to-hash (object)
  "returns a hashtable with the object's slots as keys and slot-values as values"
  (let ((hash-table (make-hash-table)))
    (loop for slot-name in (class-slots-symbols (class-of object))
          do (setf (gethash slot-name hash-table)
                   (string (slot-value object slot-name))))
    hash-table))

We can also rewrite it to slightly older style Lisp:

(defun object-to-hash (object &aux (hash-table (make-hash-table)))
  "returns a hashtable with the object's slots as keys
   and string versions of the slot-values as values"
  (dolist (slot-name (class-slots-symbols (class-of object)) hash-table)
    (setf (gethash slot-name hash-table)
          (string (slot-value object slot-name)))))

Above is much simpler and has the whole confusion about macros, generating code, compile time information vs. runtime, ... removed. It is much easier to understand, maintain and debug.

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