reddit / emacs

Elisp Struct's read-only slot dilemma

ilemming · 2022-04-29 05:29
*Note: if you just read a paragraph and having no clue what's this question is about but you'd like to learn more; I think this [post](https://nullprogram.com/blog/2018/02/14/) by Chris Wellons perfectly explains what structs are and why are they useful.* -- I'm dealing with a third-party package that uses Structures - `cl-defstruct`s. One of the slots is `:read-only t`. I need to override that. I need to find a way to change the value in that slot and then pass the structure to a function, essentially "deceiving" that function. Let's say I have: (cl-defstruct (toy (:constructor toy-create) (:copier nil)) (color nil :read-only t)) Somewhere within that third-party package, it creates an instance of `toy`, e.g.: (setq stupid-toy (toy-create :color 'red)) Now, let's say I need to change `:color` to `'green` and pass this "new object" into some function. I'd try overriding slot-accessor function - `toy-color`, right before calling that function: (cl-labels ((toy-color (x) 'green)) ;; now, whatever within (break-it) asks for toy's color, it should be 'green, right? (funcall 'break-it stupid-toy))) It seems that doesn't work. My fake `toy-color` fn doesn't get called. Maybe because `:color` is read-only? What are my options here? I cannot use `copy-toy` because copier-fn is disabled (see above) Can I create a new struct type that inherits from `toy` through `:include` option, but `:color` no longer read-only? How do I create/clone a new struct from the old one? Remember, in my example, the struct has only one slot; the real struct type that I'm dealing with has over dozen of slots - I need to find an elegant way of cloning the struct, changing a single slot value and keeping remaining slots intact.
ilemming · 2022-04-29 06:44
Holy mother of Turing, I think I figured it out. I dug into code and tried learning how `oset` works. Usually, to set a slot value you'd call it like this: `(oset stupid-toy color 'green)`, but in my case it wouldn't work, since the slot is read-only, it would complain. Turns out, `oset` internally calls `eieio-oset`. Reading its source revealed that it's possible to set the value, bypassing validation: (let* ((class (eieio--object-class stupid-toy) (idx (or (eieio--slot-name-index class 'color) (eieio--class-slot-name-index class 'color'))))) (aset stupid-toy idx 'green)) You need to figure out the index of the slot, and then you can set the value with `aset`. A bit tricky, yes, but can be done.
SlowValue · 2022-04-29 08:13
Please have a look at [emacs manual at the very bottom of this page](https://www.gnu.org/software/emacs/manual/html_node/cl/Structures.html). (cl-struct-slot-value 'toy 'color stupid-toy) ;; red (setf (cl-struct-slot-value 'toy 'color stupid-toy) 'green) ;; green (cl-struct-slot-value 'toy 'color stupid-toy) ;; green Also very helpful: read some books about `Common Lisp` (and maybe even `CLOS` could be come in handy).
ieure · 2022-04-29 16:07
I'm really curious about the usecase here. What's the third-party package, and what are you trying to make it do? Your `aset` solution is fragile, as it depends on the internal layout of the struct. If you're hardcoding the index and the struct layout changes, your code will break.
00-11 · 2022-04-29 16:34
For the info of others, the Q&A are also posted here: https://emacs.stackexchange.com/q/71572/105
ilemming · 2022-04-29 18:00
Oh wow. This is great. `setf` is awesome. Thank you! Yeah, definitely need to read some CL books.
ilemming · 2022-04-29 18:05
> I'm really curious about the usecase here. https://github.com/anticomputer/gh-notify/issues/12 > Your aset solution is fragile Indeed. Turns out there's much better and more straightforward way of setting read-only slot value.
ieure · 2022-04-29 18:17
Personally, I'd PR changes to that code rather than hack around it. I have no idea why it even exists, since it's just a copy of stuff already in `forge-notification`. It should really use that directly, where possible, and add new EIEIO methods to extend it (like for the state/date/time stuff) where needed.
SlowValue · 2022-05-02 08:19
I'm almost sure about following: It is impossible to have read only variables in Elisp or Common Lisp. Declaring a read only variable, just makes it harder to get hold onto the reference to its memory location. The same applies to C and C++. (Referencing a value from hardware ROM space, is an exception, of course).