最近想拓展一下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:
查阅以后了解 (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
来一步步调试出理想的结果的,现在对于这个宏我的疑问是:
- 为什么我在调用
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)
两者的唯一区别在于是否使用了 quote
。quote
的作用是直接返回 Symbol
对象而不是 value cell 或 function cell 中的值。如果 hello
的 value cell 中有一个函数对象的话 前者 可以正常求值,但是如果函数在 function cell 中的话只能使用 后者 了。
再来说第二个问题,为什么宏调用参数不需要加 quote
,这是因为宏不会对参数求值,宏能接收的参数就是 sexp,sexp 可以由 atom
和 cons
组成,所以你传 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 比较绕, 其他的都非常容易理解,因为说到底全部都是括号, 哈哈哈哈。