更新结构域
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-defmethod
将 setf
调用包装在 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)
在 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-defmethod
将 setf
调用包装在 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)