问题
(defstruct (mydate (:constructor make-mydate (year month day)))
(year 1970)
(month 1)
(day 1))
(defvar *date1* (make-mydate 1992 1 1))
The problem is more general, but say I would like to convert an object like date1 to a "document" I can persist to a database (e.g. mongoDB, using package cl-mongo). So I write
(defun mydate->document (mydate)
(cl-mongo:$ (cl-mongo:$ "year" (mydate-year mydate))
(cl-mongo:$ "month" (mydate-month mydate))
(cl-mongo:$ "day" (mydate-day mydate))))
REPL--> (mydate->doc *date1*)
kv-container : #(#S(CL-MONGO::PAIR :KEY year :VALUE 1992)
#S(CL-MONGO::PAIR :KEY month :VALUE 1)
#S(CL-MONGO::PAIR :KEY day :VALUE 1))
But could I, instead of having to write down all fields of my struct, obtained their names and values programmatically? After all, my lisp runtime can do that:
REPL--> (describe *date1*)
#S(MYDATE :YEAR 1992 :MONTH 1 :DAY 1)
[structure-object]
Slots with :INSTANCE allocation:
YEAR = 1992
MONTH = 1
DAY = 1
On the other hand, I did not find anything relevant in any book, and I noticed that the library cl-json cannot convert structs to JSON format (even though it does convert lists and CLOS objects). I guess that if there was a function to convert a struct to a plist, the problem would be solved.
回答1:
There is no portable way.
Implementations do it differently. Probably most have a way to access the names of the slots. It's not clear to me why such functionality is missing from the standard.
LispWorks for example has:
(structure:structure-class-slot-names (find-class 'some-structure-class))
Maybe there is already a compatibility library somewhere. It would make sense to use the Meta-Object Protocol functionality for CLOS also for structure classes.
SBCL:
* (sb-mop:class-slots (find-class 'foo))
(#<SB-PCL::STRUCTURE-EFFECTIVE-SLOT-DEFINITION A>
#<SB-PCL::STRUCTURE-EFFECTIVE-SLOT-DEFINITION B>)
* (mapcar 'sb-mop:slot-definition-name *)
(A B)
回答2:
What you are looking for is called Meta-Object Protocol.
Common lisp pioneered MOP, but only for CLOS objects, not for defstruct
objects.
Some CL implementations support defstruct MOP, but not all of them, you can check that using:
(defstruct s a)
(slot-definition-initargs (car (class-direct-slots (find-class 's))))
回答3:
This is SBCL specific:
In REPL is can do this:
(struct-to-list '(NIL #S(N :V 78) #S(OP :V #\*)
#S(E :V1 #S(N :V 456) :OP #S(OP :V #\+) :V2 #S(N :V 123))))
which will give this result =>
(NIL
((N (V 78))
((OP (V #\*)) ((E (V1 (N (V 456))) (OP (OP (V #\+))) (V2 (N (V 123)))) NIL))))
The code used:
(defun struct-names (struct)
(loop for sl in (sb-mop::class-direct-slots (class-of struct))
collect (list
(sb-mop:slot-definition-name sl)
(slot-value sl 'sb-pcl::internal-reader-function))))
(defun struct-values (struct)
(cons (type-of struct)
(loop for np in (struct-names struct)
collect (cons (car np)
(funcall (cadr np) struct)))))
(defun structp (val)
(equalp 'STRUCTURE-OBJECT
(sb-mop:class-name (car (sb-mop:class-direct-superclasses (class-of val))))))
(defun struct-to-list (val)
(cond
((structp val)
(loop for v in (struct-values val)
collect (struct-to-list v)))
((consp val)
(cons (struct-to-list (car val))
(struct-to-list (cdr val))))
(T val)))
来源:https://stackoverflow.com/questions/22084260/common-lisp-convert-unknown-struct-object-to-plist