[已解决] 修改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 还是用不了