使用结构作为 属性 列表到宏
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>)
编译此文件必须满足两个约束条件:
- 它不完全创建结构类型
mine
(参见defstruct);
- 它必须宏扩展
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))))))
所以对于这个宏,假设在扩展宏时已经看到了适合 mine
的 define-mindless-structure
形式,那么
(mindless-set= (mine it))
将扩展到
(let ((#:o it))
(set= :name (mine-name #:o) :value (mine-value #:o)))
但是,正如我所说,我不确定您真正想要的扩展是什么。
最后,在考虑使用上述任何东西之前,值得四处看看是否有可移植库提供这样的 compile/macroexpansion-time 功能:很可能有这样的,因为我跟不上事情。
我有一个包含 :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>)
编译此文件必须满足两个约束条件:
- 它不完全创建结构类型
mine
(参见defstruct); - 它必须宏扩展
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))))))
所以对于这个宏,假设在扩展宏时已经看到了适合 mine
的 define-mindless-structure
形式,那么
(mindless-set= (mine it))
将扩展到
(let ((#:o it))
(set= :name (mine-name #:o) :value (mine-value #:o)))
但是,正如我所说,我不确定您真正想要的扩展是什么。
最后,在考虑使用上述任何东西之前,值得四处看看是否有可移植库提供这样的 compile/macroexpansion-time 功能:很可能有这样的,因为我跟不上事情。