eval 和 defmacro 有啥区别呢?

参考https://github.com/syl20bnr/spacemacs/blob/develop/core/core-keybindings.el,想整一个不依赖bind-map的键位设置。

具体的代码是 ;; -- lexical-binding: t --

 (defvar cabuda-default-keymap (make-sparse-keymap))

 (defun cabuda/set-leader-keys (key def &rest bindings)
   (while key
     (define-key cabuda-default-keymap (kbd key) def)
     (setq key (pop bindings)
           def (pop bindings))))
 (put 'cabuda/set-leader-keys 'lisp-indent-function 'defun)

 (defun cabuda/init-major-leader-map (mode map)
   (let ((hook (intern (format "%s-hook" mode))))
     (or (boundp map)
         (progn
           (eval `(defvar ,map (make-sparse-keymap)))
           ;; (cabuda//setup-major-leader-map map hook)
           (eval `(add-hook ,hook (lambda ()
                      (cabuda/set-leader-keys "m" (symbol-value ,map)))))
           (boundp map))
         )))
 (defmacro cabuda//setup-major-leader-map (map hook)
   `(add-hook ,hook (lambda ()
                      (cabuda/set-leader-keys "m" (symbol-value ,map)))))
 (defun cabuda/set-major-leader-keys (mode key def &rest bindings)
   (let ((map (intern (format "cabuda-%s-map" mode))))
     (when (cabuda/init-major-leader-map mode map)
       (while key
         (define-key (symbol-value map) (kbd key) def)
         (setq key (pop bindings)
               def (pop bindings))))))

 ;; (defun cabuda/get-major-mode-keymap (mode)
 ;;   (symbol-value (intern (format "cabuda-%s-map" mode))))

 (define-key emacs-lisp-mode-map (kbd "C-c a") cabuda-default-keymap)
 (cabuda/set-major-leader-keys 'emacs-lisp-mode "f" 'helm-find-files)

主要的疑问是: image 使用宏可以满足我的要求,使用eval就不行。。请问是为什么呢?

没有吃糖。

`(add-hook ',hook
           (lambda () (cabuda/set-leader-keys "m" (symbol-value ',map))))

我简化一下来说明问题:

(defun m-fn ()
  (interactive)
  (do-something))

(defmacro set-hook (hook map)
  `(add-hook ,hook (lambda ()
                     (define-key (symbol-value ,map) (kbd "m") 'm-fn))))

(defun setup (hook map)
  (eval `(defvar ,map (make-sparse-keymap)))
  ;; (eval `(add-hook ,hook (lambda ()
  ;;                           (define-key (symbol-value ,map) (kbd "m") 'm-fn)
  ;;                        )))
  (set-hook hook map)
  )

(let ((hook (intern "foo-hook"))
      (map (intern "foo-map")))
  (setup hook map))

(princ foo-hook)
;; => ((lambda nil (define-key (symbol-value map) (kbd m) 'm-fn)))
;;                                           ^^^

首先来看宏。

使用看似宏可以通过,但是执行 (run-hooks 'foo-hook) 必然出错,因为最终生成的 lambda 里面的 map 是不存在的。应该把 map 的值先求出来,再代入:

(defmacro set-hook (hook map)
  (let ((pam (symbol-value map)))
    `(add-hook ,hook (lambda ()
                      (define-key ,pam (kbd "m") 'm-fn)))))

再来看 eval。

由于 eval 执行的时侯,变量 hookmap 都是有效的,所以 hook 可以直接使用,但 map 要先求值:

(eval `(add-hook hook (lambda ()
                        (define-key ,map (kbd "m") 'm-fn)

原先 (symbal-value ,map) 是错的,因为 ,map 已经求值了,不能对一个值再求值。

(eval `(add-hook ',hook (lambda ()
                                   (cabuda/set-leader-keys "m" ,map))))

我试了下,这样也行

我试了下你的,还是不行。。是不是要设置lexical-binding: t ?

最终版,eval和宏都可以了

;; -*- lexical-binding: t -*-

(defvar cabuda-default-keymap (make-sparse-keymap))

(defun cabuda/set-leader-keys (key def &rest bindings)
  (while key
    (define-key cabuda-default-keymap (kbd key) def)
    (setq key (pop bindings)
          def (pop bindings))))
(put 'cabuda/set-leader-keys 'lisp-indent-function 'defun)

(defun cabuda/init-major-leader-map (mode map)
  (let ((hook (intern (format "%s-hook" mode))))
    (or (boundp map)
        (progn
          (eval `(defvar ,map (make-sparse-keymap)))
          (cabuda//setup-major-leader-map map hook)
          ;; (eval `(add-hook ',hook (lambda ()
          ;;                          (cabuda/set-leader-keys "m" ,map))))
          (boundp map))
        )))
(defmacro cabuda//setup-major-leader-map (map hook)
  `(add-hook ,hook (lambda ()
                     (cabuda/set-leader-keys "m" (symbol-value ,map))))
  )
(defun cabuda/set-major-leader-keys (mode key def &rest bindings)
  (let ((map (intern (format "cabuda-%s-map" mode))))
    (when (cabuda/init-major-leader-map mode map)
      (while key
        (define-key (symbol-value map) (kbd key) def)
        (setq key (pop bindings)
              def (pop bindings))))))

(define-key emacs-lisp-mode-map (kbd "C-c a") cabuda-default-keymap)
(cabuda/set-major-leader-keys 'emacs-lisp-mode "f" 'helm-find-files)

使用宏的过程可以看作是先展开再eval,但并不等价之,可以去了解下on lisp

你最终代码的宏是错的