-
-
Save jdtsmith/5bc37c269dec2436725fd2e1fa788a54 to your computer and use it in GitHub Desktop.
(defmacro with-convenient-slots (slots obj &rest body) | |
"Convenience wrapper for read/write access to struct data. | |
Bind slot names SLOTS (a list) within the `my-struct' | |
record OBJ, and execute BODY." | |
`(cl-symbol-macrolet | |
,(cl-loop for slot in slots | |
collect `(,slot (,(intern (concat "my-struct-" | |
(symbol-name slot))) | |
,obj))) | |
,@body)) | |
;; Example usage | |
(cl-defstruct my-struct "A struct" foo bar) | |
(setq myobj (make-my-struct :foo 1 :bar 2)) | |
(with-convenient-slots (foo bar) myobj | |
(setq foo (+ 123 bar) | |
bar (+ 456 foo))) |
;; Original version: more general, but less efficient | |
(defmacro with-convenient-slots (slots obj &rest body) | |
"Convenience wrapper for read/write access to struct data. | |
Bind slot names SLOTS (a list) within the `some-data-type' | |
record OBJ, and execute BODY." | |
(declare (indent 2)) | |
`(cl-symbol-macrolet | |
,(cl-loop for slot in slots | |
collect `(,slot (cl-struct-slot-value | |
'my-struct | |
',slot ,obj))) | |
,@body)) |
@jdtsmith Thanks. A couple of other things to consider:
- Struct accessors can be named differently if so defined (see docs for
cl-defstruct
), so this macro assumes that the accessors are named with the default format. - Of course, this macro only works for one struct type,
my-struct
. And while expanding to use the accessor functions may result in more optimized code, it means that the macro only can work for one struct type; OTOH, usingcl-struct-slot-value
would allow it to work for different structs using runtime introspection. So there are pros and cons to both ways.
FWIW, if you experiment with these ideas for a while and use them "in anger," you may eventually decide that the drawbacks make macros like this not worth using. That's been my experience, anyway. But, as I mentioned on Reddit, if there were a pcase-let
-based macro that allowed setting generalized variables bound to symbols, that could be great, IMO. So if you're enthusiastic enough about this idea, I'd suggest pursuing that approach. :)
Thanks for the good points. Yes, this is not a general purpose macro, but one you would write yourself as part of your structure-based interface design. The original also encodes 'my-struct
so it's custom to this type.
I was quite surprised how heavy cl-struct-slot-value
is when you follow it down; the price you pay for generality I suppose. You could of course define your structure type as a variable for passing into the defstruct as well as the convenience macro, getting around point 2. You could even include an alternate accessor name prefix as well. Then you could re-use the macro for several different structure types and different accessor name styles (if desired). The code cleanup for reasonable size functions with well named slots makes it well worth it I think. What approaches do you use to keep structs under control?
RE pcase-let, I didn't see your post but it's an interesting idea: pcase-symbol-macrolet?
The code cleanup for reasonable size functions with well named slots makes it well worth it I think.
It depends on a variety of factors. Another cost is to code readability: when other programmers are reading your code, the more non-standard macros used in the code, the heavier the cognitive load is to understand the code.
What approaches do you use to keep structs under control?
I don't suppose that I use any, really, other than to try to keep the package name prefix and slot names short. The package I use them in the most thoroughly is https://github.com/alphapapa/ement.el. There are a few places where having a place-setting macro would be helpful, but it doesn't seem to be a big deal. I also use highlight-function-calls-mode
, which shows function names (including accessors) in a different face, which helps visually identify them and confirm that they are typed correctly, and I would miss that if I used such a macro instead.
RE pcase-let, I didn't see your post but it's an interesting idea: pcase-symbol-macrolet?
Hm, maybe so, or maybe pcase-letf
, to coincide with cl-letf
. But I'm doubtful that such a macro would be accepted into Emacs core, anyway. It would probably take some wrangling!
As pointed out by @alphapapa, the first version using
cl-struct-slot-value
lead to some inefficiencies. This version expands to identical optimized code as the normal slot accessors, ala(setf (my-struct-foo myobj) (+ 123 (my-struct-bar myobj))
.