使用结构作为 属性 列表到宏

using a struct as property list to macro

我有一个包含 :name:value 的结构,我想将其用作宏的参数。但我不确定如何告诉 lisp。

我可以把调用写成这样

(sxql:yield (sxql:set= :name "a" :value 1))

"SET name = ?, value = ?"

("a" 1)

但我想使用已经存在的结构

(defstruct my-struct name value)
(setq x (make-my-struct :name "a" :value 1))
; #S(MY-STRUCT :NAME "a" :VALUE 1)

使用来自 Common LISP: convert (unknown) struct object to plist? 的答案 我做了

(defun struct-plist (x)
  "make struct X into a property list. ugly kludge"
  (let* ((slots (sb-mop:class-slots (class-of x)))
     (names (mapcar 'sb-mop:slot-definition-name slots)))
    (alexandria:flatten
     (mapcar (lambda (n) (list (intern (string n) "KEYWORD")
                   (slot-value x n)))
         names))))
(setq p (struct-plist x)) ; (:NAME "a" :VALUE 1)

我天真的尝试是

(sxql:set= p) ; error in FORMAT: No more argument SET ~{~A = ~A~^, ~}
(funcall 'sxql:set= p) ; SXQL:SET= is a macro, not a function.
(macroexpand (sxql:set= p)) ; error in FORMAT ...

我想这是一个 easy/fundamental lisp 编程问题。但我不确定如何询问(或搜索答案)。我也希望有一个比我到目前为止偶然发现的更好的 struct<->plist 故事。

编辑:万一这真的是一个 xy 问题。我使用 flydata:defmodel 来创建结构,我想使用相同的模型插入到数据库中。

这绝对是一个 xy 问题:不幸的是,我对 y(flydata?)的理解不够好,无法回答 y 部分。

这就是您尝试执行的操作无法正常工作的原因。考虑正在编译的文件中的这段代码:

(defstruct mine name value)

...

(sxql:set= <anything derived from mine>)

编译此文件必须满足两个约束条件:

  1. 完全创建结构类型mine(参见defstruct);
  2. 它必须宏扩展 sxql:set=

这些约束的意思是sxql:set= 在扩展时无法知道结构。因此,任何依赖结构信息的技巧都必须在编译时提供该信息。

正如我所说,我对 y 部分的理解不够好,无法理解您要做什么,但是对此的一个 hacky 方法是:

  • defstruct 编写一个包装器,它在编译时(严格来说:在宏扩展时)存储信息;
  • sxql:set= 编写一个包装器,它使用该信息扩展成有意义的东西。

这是 defstruct 的无意识包装。注意这是mindless:它只能理解最简单的defstruct形式,即使那样也可能是错误的。它仅作为示例存在。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *structure-information* '()))
  
(defmacro define-mindless-structure (name &body slots)
  (assert (and (symbolp name)
               (every #'symbolp slots))
      (name slots)
    "I am too mindless")
  (let ((found (or (assoc name *structure-information*)
                   (car (push (list name) *structure-information*)))))
    (setf (cdr found) (mapcar (lambda (slot)
                                (list slot (intern (symbol-name slot)
                                                   (find-package "KEYWORD"))
                                      (intern (concatenate 'string
                                                           (symbol-name name)
                                                           "-"
                                                           (symbol-name slot)))))
                              slots)))
  `(defstruct ,name ,@slots))

所以现在

(define-mindless-structure mine
  name value)

将扩展为 (defstruct mine name value),并且 在宏扩展时 将在 *structure-information*.

中存储有关此结构的一些信息

现在我不再真正理解你需要做什么,因为我不知道 sxql:set= 是什么意思,但它可能是这样的:

(defmacro mindless-set= ((s o))
  (let ((info (assoc s *structure-information*))
        (ov (make-symbol "O")))
    (unless info
      (error "no information for ~A" s))
    `(let ((,ov ,o))
       (sxql:set= ,@(loop for (slot initarg accessor) in (cdr info)
                          ;; the compiler will whine about slot annoyingly
                          collect initarg
                          collect `(,accessor ,ov))))))

所以对于这个宏,假设在扩展宏时已经看到了适合 minedefine-mindless-structure 形式,那么

(mindless-set= (mine it))

将扩展到

(let ((#:o it))
  (set= :name (mine-name #:o) :value (mine-value #:o)))

但是,正如我所说,我不确定您真正想要的扩展是什么。


最后,在考虑使用上述任何东西之前,值得四处看看是否有可移植库提供这样的 compile/macroexpansion-time 功能:很可能有这样的,因为我跟不上事情。