[已解决] 修改S表达式指定位置的值。

我写了一个查找sexp指定位置值的函数,使用setf修改值时报错,请大家帮忙解决。

;; 函数
(defun my/get-sexp-value (location sexp)
  "Location is a list which sign the specific location in sexp."
  (let ((value sexp))
    (dolist (num location)
      (setq value (nth num value)))
    value))
;; 使用
(setq test '(1 2 (3 4) 5 (6 (7 8) 9)))
(my/get-sexp-value '(2 0) test) ==> 3
(my/get-sexp-value '(4 1 1) test) ==> 8

现在我想修改获取的指定位置的值,使用setf函数:

(setf (my/get-sexp-value '(4 1 1) test) 10)

按理应该将8修改为10,但是报错:

(void-function \(setf\ my/get-sexp-value\))。

正常使用:

(setf (nth 1 (nth 1 (nth 4 test))) 10)

可以修改值,为什么自己写的函数就不行?我想是setf函数的用法问题。如果这种方法无法修改,有没有其他的方法?求教!

因为 setf 要定义 expander 才能用。

gv-define-expander is an autoloaded Lisp macro in ‘gv.el’.

(gv-define-expander NAME HANDLER)

Use HANDLER to handle NAME as a generalized var.
NAME is a symbol: the name of a function, macro, or special form.
HANDLER is a function which takes an argument DO followed by the same
arguments as NAME.  DO is a function as defined in ‘gv-get’.

写成宏,展开得到以下,或许可行(我没试过)。

怎么写…?

谢谢,有相关文档或详细的例子吗? 在elisp manual上找到了。https://www.gnu.org/software/emacs/manual/html_node/elisp/Adding-Generalized-Variables.html

(defun my-nth-helper (lst acc)
  (if lst
      (my-nth-helper (cdr lst)
                     (list `(nth ,(car lst) ,@acc)))
    acc))

;; (my-nth-helper '(1 2 3) '(lst))
;; => ((nth 3 (nth 2 (nth 1 lst))))

(defmacro my-nth (ns list)
  (car (my-nth-helper ns `(,list))))

(let ((test '(1 2 (3 4) 5 (6 (7 8) 9))))
  (my-nth (4 1 1) test))
;; => 8

(let ((test '(1 2 (3 4) 5 (6 (7 8) 9))))
  (setf (my-nth (4 1 1) test) 10)
  test)
;; => (1 2 (3 4) 5 (6 (7 10) 9))

注意 Quoted 的是常量,不应该修改,最好改成下面的写法,否则这段代码原封不动放到函数会出问题。

(list 1 2 (list 3 4) 5 (list 6 (list 7 8) 9))
1 个赞

没必要用macro,用compiler macro就行,以前在论坛讲过compiler macro,用了macro就没法用apply调用了

https://cireu.github.io/2019/09/10/elisp-compiler-macs/#广义变量展开中的compiler-macro

Example: https://github.com/Wilfred/ht.el/blob/master/ht.el#L110

1 个赞

按照 Common Lisp 标准来的话 compiler macro 不保证一定会被展开。而且就算是 Emacs Lisp 的话不 byte compile 一样不会展开。尤其是配置文件通常都是不 byte compile 的。

1 个赞

这是Emacs Lisp版 XD,而且Emacs source tree里也有用compiler macro优化的,我想他们不至于把这东西砍掉

有load time的eager macro expansion(since 24),你直接用eval试的话倒是不能展开

另外setf会手动用macroexpand-all打开compiler macro

然而用 compiler macro 还是要写一遍 defun 定义,和写 setf expander 一样要写两遍。不然 funcall 和 apply 还是用不了