Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

common lisp - using a struct as property list to macro

I have a struct with :name and :value that I'd like to use as arguments to a macro. But I'm not sure how to tell lisp that.

I can write out the call like

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

"SET name = ?, value = ?"

("a" 1)

But I'd like to use an already existing structure

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

using answers from Common LISP: convert (unknown) struct object to plist? I've made

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

My naive attempts are

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

I imagine this is an easy/fundamental lisp programming question. But I'm not sure how to ask it (or search for answers). I'm also hoping there is an better struct<->plist story than what I've stumbled across so far.

EDIT: In case this is really an xy-problem. I've used flydata:defmodel to create the struct and I want to insert to a database using the same model.

question from:https://stackoverflow.com/questions/65649667/using-a-struct-as-property-list-to-macro

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is definitely an xy problem: unfortunately I don't understand y (flydata?) well enough to answer the y part.

Here's why what you are trying to do can't work however. Consider this code in a file being compiled:

(defstruct mine name value)

...

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

Compiling this file must satisfy two constraints:

  1. It does not fully create the structure type mine (see defstruct);
  2. It must macroexpand sxql:set=.

What these constraints mean is that sxql:set= can't know about the structure at the time it is expanded. So any trick which relies on information about the structure must make that information available at compile time.

As I said, I don't understand the y part well enough to understand what you are trying to do, but a hacky approach to this is:

  • write a wrapper for defstruct which stashes information at compile time (strictly: at macro-expansion time);
  • write a wrapper for sxql:set= which uses that information to expand into something which makes sense.

Here is a mindless wrapper for defstruct. Note that this is mindless: it can only understand the most simple defstruct forms, and even then it may be wrong. It exists only as an example.

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

So now

(define-mindless-structure mine
  name value)

Will expand into (defstruct mine name value) and, at macroexpansion time will stash some information about this structure in *structure-information*.

Now I stop really understanding what you need to do because I don't know what sxql:set= is meant to do, but it might be something like this:

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

So with this macro, assuming a suitable define-mindless-structure for mine form has been seen by the time the macro is expanded, then

(mindless-set= (mine it))

Will expand into

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

But, as I said, I am not sure what the expansion you actually want is.


Finally, before even thinking about using anything like the above, it would be worth looking around to see if there are portability libraries which provide compile/macroexpansion-time functionality like this: there very well may be such, as I don't keep up with things.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...