(setq list-of-list '(((value . (lambda () '("1" "2"))) (prop . 0.2) (candidate . t))
((value . (lambda () '("1" "3"))) (prop . 0.4) (face . ((t (:foreground "red")))))
((value . (lambda () '("2" "4"))) (prop . 0.4) (face . ((t (:foreground "green")))))))
(defun fun (list-of-list)
(dolist (alist list-of-list)
(let ((func (alist-get 'value alist)))
(setf (alist-get 'value alist) (funcall func)))))
(fun list-of-list)
list-of-list
; => (((value "1" "2") (prop . 0.2) (candidate . t)) ((value "1" "3") (prop . 0.4) (face (t ...))) ((value "2" "4") (prop . 0.4) (face (t ...))))
发现list-of-list
被修改了,从(lambda () '("1" "2")))
变成了("1" "2")
。
而另一个实验中,list-of-list
没有被改变(期望行为)
(setq s1 "s"
s2 "s"
s3 "s")
(setq list-of-list '(((value . s1) s2 s3) ((value . s3) s2 s1)))
(defun fun (list-of-list)
(dolist (list list-of-list)
(fset (alist-get 'value list) '(value . "ss"))))
(fun list-of-list)
list-of-list
; => (((value . s1) s2 s3) ((value . s3) s2 s1))
这是为什么啊?
害得我折腾两个小时
副作用?
(setq s1 "s1"
s2 "s2"
s3 "s3")
(setq foo (list s1 s2 s3))
(setf (cadr foo) 2)
foo ;; => ("s1" 2 "s3")
(defun test (list)
(setf (cadr list) 20))
(test foo)
foo ;; => ("s1" 20 "s3")
同理 setcar
,setcdr
和 nconc
,危害大。
第二个实验
(mapcar (lambda (x)
(symbol-function (alist-get 'value x)))
list-of-list)
;; => ((value . "ss") (value . "ss"))
你都用了 setf 为什么会改变心理就没点数?
还是说你都不知道 setf 和 fset 的作用是完全两回事?
哦,你以为 lexical binding 作用是 copy value 吧?果然是被 Python 害的。Copy by reference 了解一下。
这和 lexical scoping 一点关系都没有,还不是你乱取参数名把自己玩糊涂了,就算用
(defun fun (abc)
(dolist (list abc)
(fset (alist-get 'value list) '(value . "ss"))))
结果也是一样的。
不清楚你的疑问在哪里,或许你没注意 setf
和 fset
的区别?
(let ((pair '(k . v)))
(setf (cdr pair) 100)
pair)
;; => (k . 100)
(let ((pair '(k . v)))
(fset (cdr pair) (lambda () 100))
(cons pair (funcall (cdr pair))))
;; => ((k . v) . 100)
你大概是混淆了这两个函数的作用,不关 lexical binding 的事。
setf
应该跟 setq
对比
-
(setq SYM VAL SYM VAL ...)
修改符号对应的值
-
(setf PLACE VAL PLACE VAL ...)
修改位置对应的值
关键就在于 PLACE,它不是符号:
(let ((al '((foo . 1)
(bar . 2))))
(setf (car (assoc 'foo al)) 'quux) ;; 修改 key
al)
;; => ((quux . 1) (bar . 2))
alist 中的这些 “key” 不能算做符号,只是数据而已。换一种写法可能更清晰:
(let ((al '(("foo" . 1)
("bar" . 2))))
(setf (car (assoc "foo" al)) "quux") ;; 修改 key
al)
;; => (("quux" . 1) ("bar" . 2))
fset
则…仍然可以跟 setq
对比
-
(setq SYM VAL SYM VAL ...)
修改符号对应的值
-
(fset SYM DEFINITON)
修改符号对应的定义
虽然参数都是 SYM,但实际是不同的东西:
(defun foo () (princ "FOO\n"))
(foo)
;; => FOO
;; 创建了一个新的符号:变量 foo
(setq foo
(lambda () (princ "BAR\n")))
;; 所以函数行为不变
(foo)
;; => FOO
defun
出来的符号,用 fset
才能修改:
(defun foo () (princ "FOO\n"))
(foo)
;; => FOO
;; 修改的是原来的符号 foo 的定义
(fset 'foo
(lambda () (princ "BAR\n")))
;; 所以函数行为改变
(foo)
;; => BAR
用 setf
实现 fset
的效果:
(defun foo () (princ "FOO\n"))
(foo)
;; => FOO
-- ;; 修改的是原来的符号 foo 的定义
-- (fset 'foo
++ ;; 替换符号 foo 定义的值
++ (setf (symbol-function 'foo)
(lambda () (princ "BAR\n")))
;; 结果改变
(foo)
;; => BAR
虽然 setf
也能达到相同效果,但思路是不一样的。
打个不恰当的比喻,就像是 c 语言里修改指针内容:
#+BEGIN_SRC C :results output :includes <stdio.h> :flags -std=gnu99 -Wall -Werror
char s1[10] = {'f', 'o', 'o'};
char s2[10] = {'b', 'a', 'r'};
char *p = s1;
printf("%s\n", p);
// fset -- 指向新的内容
p = s2;
printf("%s\n", p);
// setf -- 原地修改内容
sprintf(p, "qux");
printf("%s\n", p);
#+END_SRC
#+RESULTS:
: foo
: bar
: qux
2 个赞
谢谢大家,我还有很多要学习啊。打算近期看看书补充一下基础知识。
学习了,已收藏。一点小建议,第二段 snippet 第一次直接调用,第二次用 funcall
,容易让人以为 fset
的函数和普通定义的函数调用方式不同。我刚才就产生这样的疑惑,然后自己运行一下才发现其实没有什么不同,(foo)
这样调用也可以。
另外,以我的理解 cl-letf
的用法和 setf
是类似的吧? flet
和 fset
类似?
改了。
本来都是 (funcall foo)
,从 org 粘贴过来的时候临时改成 (foo)
,然后没改完。