add-hook和lambda ?

楼上诸位展开太深了。

如果只是回答:

很简单,一个字:懒

懒惰的写法:

(add-hook 'xxx-mode-hook
          (lambda ()
            ...))

不厌其烦的写法:

(defun foo (lambda ()
            ...))

(add-hook 'xxx-mode-hook 'foo)

楼主后面又问了,为什么 add-hook 第二参数必须是 FUNCTION,不能是 S 表达式。答案也很简单:因为 add 之后,将由 (funcall FUNCTION) 来执行传进来的函数,如果不是函数就会出错。而参数列表本身不具备约束力,传什么都可以。

接下来就是函数名的问题了。函数执行之前,首先要对其参数进行求值,所以如果直接写:

(add-hook 'xxx-mode-hook foo)

那么 foo 就会被当作变量处理了,可以通过 M-x ielm 来观察这一求值过程:

ELISP> (defun foo () (message "foo"))
foo
ELISP> foo
*** Eval error ***  Symbol’s value as variable is void: foo
ELISP> 'foo
foo

ELISP> (defvar bar "bar")
bar
ELISP> bar
"bar"
ELISP> 'bar
bar

所以写成 'foo 确保传进去的是 SYMBOL:

(add-hook 'xxx-mode-hook 'foo)
;; 等效
;; (add-hook (quote xxx-mode-hook) (quote foo))

虽然传递函数名和 lambda 执行效果没什么区别,但还是有些需要注意的,例如:

(add-hook 'xxx-mode-hook 'foo) 之后,xxx-mode-hook 的内容是

(foo ;; <-- 最近添加
 bar
 quux
 ...)

(add-hook 'xxx-mode-hook (lambda () (message "foo"))) 之后,其内容则是

((lambda () (message "foo")) ;; <-- 最近添加
 bar
 quux
 ...)

所以如果是传递的是函数名,将来需要改变函数 foo 的行为,直接修改就可以了。但如果传递的是 (lambda ...),要修改就比较麻烦了,必须整个 (lambda ..) 一字不漏抄一遍:

(remove-hook 'xxx-mode-hook (lambda () (message "foo")))

然后,修改,重新添加:

(add-hook 'xxx-mode-hook (lambda () (message "bar")))

所以说,偷懒是有代价的。

(defun ...)(lambda ...) 的关系,对应到 c 语言,我感觉因类似指针和结构体的关系(毕竟 c 不能把函数当值传递):

(add-hook 'xxx-mode-hook 'foo)           ;; 指针(传引用)
(add-hook 'xxx-mode-hook (lambda ()...)) ;; 结构体(传值,一大拖东西)

实际上,(defun ...) 展开之后也是个 lambda,只不过有了一个别名,例如 foo:

(defun foo ()
  (message "foo"))

;; 展开 =>

(defalias (quote foo)
  (function
   (lambda nil (message "foo"))))
10 个赞