更新结构域

Update struct field

在 Elisp 中,是否有更符合人体工程学的方法将函数应用于结构中的字段?

假设我有以下内容:

(cl-defstruct stack xs)

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

有没有一种简单的方法可以将函数应用于 :xs 字段。我想要这样的 API:

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

有人知道这是否存在吗?

更新: 我正在寻找一种方法来 DRY 对 (stack-xs stack) 的调用(见下文)。我正在寻找的东西更类似于 Elixir 中的 Map.update

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

cl-defstruct 宏创建形式为 NAME-SLOT 的插槽访问器,其中 NAME 是结构类型名称,SLOT 是插槽名称。使用您的示例,您可以使用 setf 和插槽访问器设置 xs 插槽,如下所示:

(cl-defstruct stack xs)
(defvar st (make-stack :xs '(1 2 3 4 5)))
(setf (stack-xs st) (cl-map 'list #'1+ (stack-xs st)))
(stack-xs st)

最后一行 returns '(2 3 4 5 6).

更新: 上面显示的 setf 调用的一个缺点是插槽访问器必须使用两次,一次读取当前值,然后再次读取当前值将其更新为新值。您可以使用 cl-callf 将其干燥:

(cl-callf (lambda (p) (cl-map 'list #'1+ p)) (stack-xs st))

或者,您可以使用 cl-defmethodsetf 调用包装在 stack 类型上定义的新方法中,也许像这样:

(cl-defmethod stack-update ((s stack) f slot)
  "Update SLOT in stack S by applying F.
F is passed one argument, the current value of SLOT,
and is expected to return a new value for SLOT."
  (let ((sl (if (keywordp slot) (intern (substring (symbol-name slot) 1)) slot)))
    (setf (cl-struct-slot-value 'stack sl s) (funcall f (cl-struct-slot-value 'stack sl s)))))

然后您可以这样调用 stack-update

(stack-update st #'(lambda (p) (cl-map 'list #'1+ p)) :xs)

或等同于:

(stack-update st (apply-partially 'cl-map 'list #'1+) 'xs)

我最终通过编写宏解决了这个问题,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)))

我这样使用这个宏:

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

这将更新以下结构:

(cl-defstruct set xs)