【问题标题】:Common Lisp Macros: correct expansion of a generated listCommon Lisp 宏:生成列表的正确扩展
【发布时间】:2011-06-25 20:00:53
【问题描述】:

我正在构建一种机制来获取任意 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)))))

在macroexpand-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))))

显然,问题在于 slot-list 没有被扩展。 (对我而言)不太明显的是解决方案。


跟进:在 Rainer 指出我正确的方向之后:

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

【问题讨论】:

  • 为什么是宏?显而易见的解决方案是将其编写为函数。
  • @Rainer:因为 type-of 需要一个符号。将*bar* 作为函数传入会导致*bar* 所指的对象被使用,这不是我想要的。 (不过,也许我的想法是错误的)
  • 符号的类型是符号。无需计算。如果你想要一个对象的类,只需调用函数 CLASS-OF。再想想。将其重写为函数。不需要宏。

标签: macros common-lisp expansion


【解决方案1】:

只看它,我看不出它为什么应该是一个宏。将其重写为函数会省去很多麻烦。

您尝试使用 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"

糟糕的文档:你什么都不读。读取是 I/O 操作,在您的代码中没有。您正在谈论一个“对象”,但在上面您有类似“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 采用读取位置的形式和计算值的形式。就这样。它适用于各种数据结构。人们再也不需要记住 writer 函数的样子(名称、参数列表……)。

))))

这是我的版本

请注意,我使用的是包 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)))))

上面的内容要简单得多,并且对宏、生成代码、编译时间信息与运行时等方面的所有混淆...已删除。它更容易理解、维护和调试。

【讨论】:

  • 槽值做到了。谢谢。多谢。 :-)
  • 跟进:我使用 with-slots 因为我不知道 slot-value。 With-slots 需要使用扩展的插槽列表,因此使用宏。使用 class-of 也略有简化。我的最终解决方案贴在上面:它看起来很像你的。
  • @Paul Nathan:'因此使用宏'-但这不起作用。您无法在运行前知道对象的插槽。编译器根本不知道它们。使用宏不能解决这个问题。
猜你喜欢
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 2017-01-24
  • 2016-02-17
  • 2018-09-06
  • 1970-01-01
  • 2020-09-18
  • 1970-01-01
相关资源
最近更新 更多