Search code examples
elisp

Update struct field


Is there a more ergonomic way to apply a function to a field in a struct in Elisp?

Say I have the following:

(cl-defstruct stack xs)

(defvar stack (make-stack :xs '(1 2 3 4 5)))

Is there an easy way to apply functions to the :xs field. I'd like an API like this:

(update-field :xs stack (lambda (xs)
                          (cl-map 'list #'1+ '(1 2 3 4 5))))

Does anyone know if this exists?

Update: I'm looking for a way to DRY up the calls to (stack-xs stack) (see below). What I'm looking for is more similar to something like Map.update from Elixir.

(setf (stack-xs stack) (cl-map 'list #'1+ (stack-xs stack)))

Solution

  • I ended up solving this by writing a macro, struct/update:

    (defmacro struct/update (type field f xs)
      "Apply F to FIELD in XS, which is a struct of TYPE.
    This is immutable."
      (let ((copier (->> type
                         symbol-name
                         (string/prepend "copy-")
                         intern))
            (accessor (->> field
                           symbol-name
                           (string/prepend (string/concat (symbol-name type) "-"))
                           intern)))
        `(let ((copy (,copier ,xs)))
           (setf (,accessor copy) (funcall ,f (,accessor copy)))
           copy)))
    

    I use this macro as such:

    (defun set/add (x xs)
      "Add X to set XS."
      (struct/update set
                     xs
                     (lambda (table)
                       (let ((table-copy (ht-copy table)))
                         (ht-set table-copy x 10)
                         table-copy))
                     xs))
    

    Which will update the following struct:

    (cl-defstruct set xs)