elisp的宏:quote一个 `(make-symbol "xxx")` 返回的对象到底是什么,为什么要quote一个symbol

最近想拓展一下evil的textobjects,网上搜了一下这个stackoverflow给了这样的一段宏:

(defmacro define-and-bind-text-object (key start-regex end-regex)
  (let ((inner-name (make-symbol "inner-name"))
        (outer-name (make-symbol "outer-name")))
    `(progn
       (evil-define-text-object ,inner-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count nil))
       (evil-define-text-object ,outer-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count t))
       (define-key evil-inner-text-objects-map ,key (quote ,inner-name))
       (define-key evil-outer-text-objects-map ,key (quote ,outer-name)))))

调用这段宏会有如下的效果

(my-define-and-bind-text-object "~" "~" "~")
(my-define-and-bind-text-object "|" "|" "|")

会在 evil-inner-text-object-map 创建两个同名但是不同功能的 map:

Screen Shot 2022-09-19 at 00.44.30

查阅以后了解 (make-symbol "xxx") 可以生成两个名字相同但是其实并非相同的两个 symbol,但是我还是不好理解为什么这段宏的最后一行代码

(define-key evil-inner-text-objects-map ,key (quote ,inner-name))

为什么要把 ,inner-name 也要 quote 起来呢?按道理来说 ,inner-name 因为前面有一个逗号,已经是求值后返回 (make-symbol "inner-name") 所生成的那个symbol了,为什么要quote一个symbol?

仿照着这个函数,我自己实现了一个用来定义给mode来定义local textobject的宏:

(defmacro my-define-and-bind-local-text-object (key start-regex end-regex hook)
  (let ((inner-name (make-symbol "inner-name"))
        (outer-name (make-symbol "outer-name")))
    `(add-hook ',hook
      (lambda ()
        (evil-define-text-object ,inner-name (count &optional beg end type)
          (evil-select-paren ,start-regex ,end-regex beg end type count nil))
        (evil-define-text-object ,outer-name (count &optional beg end type)
          (evil-select-paren ,start-regex ,end-regex beg end type count t))
        (define-key evil-operator-state-local-map ,(concat "i" key) (quote ,inner-name))
        (define-key evil-operator-state-local-map ,(concat "a" key) (quote ,outer-name))
        (define-key evil-visual-state-local-map ,(concat "i" key) (quote ,inner-name))
        (define-key evil-visual-state-local-map ,(concat "a" key) (quote ,outer-name))
        ))))

(my-define-and-bind-local-text-object "`" "`" "'" emacs-lisp-mode-hook)

我是通过调用 macro-step-expand 来一步步调试出理想的结果的,现在对于这个宏我的疑问是:

  1. 为什么我在调用my-define-and-bind-local-text-object 的时候,不需要将 emacs-lisp-mode-hook quote 起来?

一个一个说吧

对于 LZ 的第一个问题,即“在宏的最后一段的 define-key 那里为什么需要 quote 一下 symbol。相信 LZ 也使用过 macro-expand 函数来观察宏展开的效果,比如下面这个宏:

(defmacro yy-key (key name)
  `(define-key my-map ,key ,name))
(macroexpand '(yy-key "a" hello))
=> (define-key my-map "a" hello)

(defmacro yy-key-1 (key name)
  `(define-key my-map ,key ',name))
(macroexpand '(yy-key-1 "a" hello))
=> (define-key my-map "a" 'hello)

两者的唯一区别在于是否使用了 quotequote 的作用是直接返回 Symbol 对象而不是 value cell 或 function cell 中的值。如果 hello 的 value cell 中有一个函数对象的话 前者 可以正常求值,但是如果函数在 function cell 中的话只能使用 后者 了。


再来说第二个问题,为什么宏调用参数不需要加 quote ,这是因为宏不会对参数求值,宏能接收的参数就是 sexp,sexp 可以由 atomcons 组成,所以你传 emacs-lisp-mode-hook 进去就是传了个 symbol ,如果传 'emacs-lisp-mode-hook 实际上传的是 (quote emacs-lisp-mode-hook) 这个表,反而不对。

关于 symbol 对象的组成可以参考 elisp manual 的 Symbol 一章。

感谢!第二个情况我其实多调试了几次macro-step-expand以后大概猜到会发生什么了,第一个例子我看了你的解答以后就理解了。 感觉lisp系的symbol,variable,value之间互有区别又互相联系真的好复杂😂一开始我是按照c语言的指针和解引用去理解,但是遇到了这个例子我就完全懵逼了,看来真的不是那么一回事。 感觉用emacs后开始去理解lisp这个比c语言还古老的语言真的有点特别的意思,一方面它是动态语言,在执行层面上非常不底层,另一方面它的语义层面上又感觉非常底层,涉及到了很多主流编程语言不存在的概念

elisp 里面大多数的值都是指针,除了定长整数 fixnum。

quote 用在 Symbol 上的话就是直接返回一个指向 Symbol 对象指针的意思。用在其他值上也差不多,但是其他的值(list不行)大多是可以自求值的,所以也不太需要

Lisp语言其实都是意识流风格的,习惯以后, 除了看不到实现的一部分 macro 比较绕, 其他的都非常容易理解,因为说到底全部都是括号, 哈哈哈哈。