Common Lisp Macros: правильное расширение сгенерированного списка - PullRequest
0 голосов
/ 26 июня 2011

Я создаю механизм для того, чтобы взять произвольный объект CLOS и вернуть из него хеш (полезно в моем опыте отладки).

Однако я не уверен, как заставить расширение переменной. Я чувствую, что решение заключается в правильном использовании gensym, но я не знаю, как.

;;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)))))

После макроэкспанда-1 я обнаружил, что это расширяется до следующего кода (*bar* является объектом класса):

(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))))

Очевидно, проблема в том, что список слотов не расширяется. Менее очевидное (для меня) решение.


Продолжение: После того, как Райнер указал мне правильное направление:

(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))

1 Ответ

7 голосов
/ 26 июня 2011

Просто глядя на это, я не вижу причин, почему это должен быть макрос.Переписав его как функцию, вы избавите вас от многих проблем.

Использование WITH-SLOTS невозможно, как вы пытаетесь.Объект не известен вообще до времени выполнения.Компилятор должен знать слоты объекта уже во время компиляции.Вам нужно использовать SLOT-VALUE и искать значение слота во время выполнения.

Вы думаете, во многих отношениях слишком сложно, и ваш код немного запутан.Вы можете избавиться от путаницы, следуя простым правилам и избегая формулировок.

Давайте посмотрим на ваш код :

Во-первых, это не вспомогательный макрос, посколькучто следует за функцией.

;;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)

Плохая формулировка: obj-inst.Либо назовите это объектом или экземпляром.Не оба.

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

Плохая документация: вы ничего не читаете.Чтение - это операция ввода-вывода, а в вашем коде - нет.Вы говорите об «объекте», но выше у вас есть что-то вроде «obj-inst».Зачем говорить об одном и том же двумя разными способами?Вы можете документировать, какие хеш-таблицы являются фактическими картами.От каких ключей к каким значениям?

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

new-hash - тоже плохое имя.По сути, это хеш-таблица.

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

Почему TYPE-OF, а затем в вызове вспомогательной функции FIND-CLASS?Common Lisp имеет CLASS-OF, который возвращает класс напрямую.

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

Выше не будет работать, так как WITH-SLOTS ожидает имена слотов во время компиляции, а не список слотов.

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

HASHSET не нужен, если он не делает что-то особенное.Обычный способ установки значений - через SETF.SETF принимает форму для чтения места и форму для вычисления значения.Это все.Это работает для всех видов структур данных.Больше никогда не нужно вспоминать, как выглядит функция записи (имя, список параметров, ...).

))))

Вот моя версия :

Примечаниечто я использую пакет CLOS, вы можете использовать свой пакет 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)))

Выше приведена простая функция, принимающая класс и возвращающая список имен слотов.

Далее,у нас есть простая функция, которая в этой форме была написана миллион раз в 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))

Мы также можем переписать ее на немного более старый стиль 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)))))

вышеэто намного проще и содержит всю путаницу с макросами, генерацией кода, информацией о времени компиляции и временем выполнения, ... удалена.Гораздо проще понять, поддерживать и отлаживать.

...