帮我看看这个宏是哪有问题

(defmacro with-mode-on (mode &rest body)
  (declare (indent defun)
           (doc-string 3))
  `(let ((,(make-symbol (concat (symbol-name mode) "-p")) ,mode))
     (unless ,(make-symbol (concat (symbol-name mode) "-p")) (,mode 1))
     ,@body
     (unless ,(make-symbol (concat (symbol-name mode) "-p")) (,mode -1))))

(macroexpand '(with-mode-on icomplete-mode (message "ss")))

展开后的结果是,看上去似乎没什么问题

(let ((icomplete-mode-p icomplete-mode))
  (unless icomplete-mode-p (icomplete-mode 1))
  (message "ss")
  (unless icomplete-mode-p (icomplete-mode -1)))

但是执行的时候报

(with-mode-on icomplete-mode (message "ss"))


let: Symbol’s value as variable is void: icomplete-mode-p

每个make-symbol生成的symbol都是独一无二(uninterned symbol)的,如果不这么做,就没法根本上保证用了gensym之后宏可以卫生,这和intern生成的symbol不同,intern生成的symbol会放入变量obarray保存起来。有

(eq (make-symbol "sym") (make-symbol "sym"))
;; => nil

(eq (intern "sym") (intern "sym"))
;; => t

正确的玩法是用let保存一个uninterned symbol到变量里,然后用这个变量。

(defmacro with-mode-on (mode &rest body)
  (declare (indent defun)
           (doc-string 3))
  (let ((sym (make-symbol (concat (symbol-name mode) "-p"))))
    `(let ((,sym ,mode))
       (unless ,sym (,mode 1))
       ,@body
       (unless ,sym (,mode -1)))))
1赞

对于这种把一个sexp当成表达式赋值到一个临时变量的用法,Emacs 24.3后引入了macroexp-let2

(defmacro with-mode-on (mode &rest body)
  (declare (indent defun)
           (doc-string 3))
  (macroexp-let2 nil mode-p mode
    `(progn
       (unless ,mode-p (,mode 1))
       ,@body
       (unless ,mode-p (,mode -1)))))

展开后(#:是uninterned symbol的读入形式,不要在意)

(let*
    ((#:mode-p icomplete-mode))
  (progn
    (unless #:mode-p
      (icomplete-mode 1))
    (message "ssa")
    (unless #:mode-p
      (icomplete-mode -1))))

我展开之后的结果似乎跟你的不太一样


(defmacro with-mode-on (mode &rest body)
  (declare (indent defun)
           (doc-string 3))
  (macroexp-let2 nil mode-p mode
    `(progn
       (unless ,mode-p (,mode 1))
       ,@body
       (unless ,mode-p (,mode -1)))))


(macroexpand '(with-mode-on icomplete-mode (message "ss")))
(let* ((mode-p icomplete-mode)) (progn (unless mode-p (icomplete-mode 1)) (message "ss") (unless mode-p (icomplete-mode -1))))

(let ((print-gensym t)
      (print-circle nil))
  (pp (macroexpand '(with-mode-on icomplete-mode (message "ssa")))))

其实这样更能说明问题

(let ((print-gensym t)
      (print-circle t))
  (pp (macroexpand '(with-mode-on-old icomplete-mode (message "ssa")))))
;; (let
;;     ((#:icomplete-mode-p icomplete-mode))
;;   (unless #:icomplete-mode-p
;;     (icomplete-mode 1))
;;   (message "ssa")
;;   (unless #:icomplete-mode-p
;;     (icomplete-mode -1)))
(let ((print-gensym t)
      (print-circle t))
  (pp (macroexpand '(with-mode-on icomplete-mode (message "ssa")))))
;; (let*
;;     ((#1=#:mode-p icomplete-mode))
;;   (progn
;;     (unless #1#
;;       (icomplete-mode 1))
;;     (message "ssa")
;;     (unless #1#
;;       (icomplete-mode -1))))