这一帖我们介绍一下剩下的用法,给出 CL 和 elisp 中各自的简化版 backquote
实现,并根据实现来分析 elisp 不同于 CL 的原因。
我不是太擅长写教程。。。这一节从嵌套用法介绍到 backquote
实现的承接可能有点生硬。
④ ,,@
和 ,',@
,,@
表示先展平,后对展平的各项求值:
* ``(,,@(list '(+ 1 2) '(+ 2 3)))
`(,(+ 1 2) ,(+ 2 3))
* `(,(+ 1 2) ,(+ 2 3))
(3 5)
,',@
类似于上面 ③ 中加 '
的情况,但要注意 ,@
得到的结果只能有一项,否则就会出错:
* ``(,',@(list '(+ 1 2)))
`(,'(+ 1 2))
* `(,'(+ 1 2))
((+ 1 2))
* ``(,',@(list '(+ 1 2) '(+ 2 3)))
`(,(QUOTE (+ 1 2) (+ 2 3)))
* `(,(QUOTE (+ 1 2) (+ 2 3)))
debugger invoked on a SIMPLE-ERROR in thread
#<THREAD "main thread" RUNNING {10010B0523}>:
wrong number of args to QUOTE:
(QUOTE (+ 1 2) (+ 2 3))
⑤ ,@,@
和 ,@',@
,@,@
表示进行二次展平, ,@',@
和 ,',@
类似,后一 ,@
展开得到的结果只能有一项:
* ``(,@,@(list '(list 1 2) '(list 3 4)))
`(,@(LIST 1 2) ,@(LIST 3 4))
* `(,@(LIST 1 2) ,@(LIST 3 4))
(1 2 3 4)
* ``(,@',@(list '(list 1 2)))
`(,@'(LIST 1 2))
* `(,@'(LIST 1 2))
(LIST 1 2)
对于 ④ 和 ⑤ ,elisp 中都无法得到令人满意的结果,这是由于 elisp 的 backquote
不符合 CL 标准而导致的,在上上贴的连接中,作者这样说到:
the rules given here work, but some Common Lisp implementations have run into trouble at one time or another by using a simplification rule that does not work in all cases.
;;elisp
``(,,@(list '(+ 1 2) '(+ 3 4)))
=> `((\, (+ 1 2) (+ 3 4)))
=> error "Multiple args to , are not supported"
``(,,@(list '(+ 1 2)))
=> `(,(+ 1 2))
=> (3)
``(,@,@(list '(list 1 2) '(list 3 4)))
=> `((\,@ (list 1 2) (list 3 4)))
=> error
``(,@,@(list '(list 1 2)))
=> `(,@(list 1 2))
=> (1 2)
按照 CL 标准,上面的各表达式展开结果应该是这样的(方便起见,这里就只展开最里层说明一下):
``(,,@(list '(+ 1 2) '(+ 3 4)))
`(append (list ,@(list '(+ 1 2) '(+ 3 4))) nil)
(eval it) => (append (list (+ 1 2) (+ 3 4)) nil)
(eval it) => (3 7)
``(,@,@(list '(list 1 2) '(list 3 4)))
`(append ,@(list '(list 1 2) '(list 3 4)) nil)
(eval it) => (append (list 1 2) (list 3 4) nil)
(eval it) => (1 2 3 4)
本贴剩下的内容是对 CL 和 elisp 实现的一个简单分析,首先我们给出 CL 的一个极简实现:(此处的实现参考了上上贴中的链接)
出于简单考虑,我没有对展开时进行化简,且没有实现 ,.
;; ec$ 即 backuqote
;; ec% 即 unquote
;; ec%@ 即 unquote-splicing
(defmacro ec$ (x)
"the backquote macro
ec means emacs-china, bk means backquote"
(ec-bk-process x))
(defun ec-bk-process (x)
(cond
((atom x) ;;原子类型直接用 quote
(list 'quote x))
((eq (car x) 'ec$) ;;嵌套 ` 处理,即递归处理,先从最里层开始
(ec-bk-process (ec-bk-process (cadr x))))
((eq (car x) 'ec%) ;;逗号处理,即 ,expr
(cadr x)) ;; (unquote expr)
((eq (car x) 'ec%@) ;;`的后面不应直接出现 ,@
(error ",@~S after `" (cadr x)))
(t ;; 处理 `(a1 a2 ... an) 的情况
(do ;;使用 ec-bracket 处理表中的每一项
((p x (cdr p))
(q '() (cons (ec-bracket (car p)) q)))
((atom p) ;;结束条件
(cons 'append
(nreconc q (list (list 'quote p)))))
;;中途检查
(when (eq (car p) 'ec%) ;; 遇到了落单的 `,'
;;它只能以 `(e1 e2 ... . ,e-last) 的形式出现
;;一般情况下,待处理表的形式是 ((unquote ...) ...),取其 car 得到的是表而不是符号
(unless (null (cddr p)) (error "Malformed ,~S" p))
(return (cons 'append (nreconc q (list (cadr p))))))
(when (eq (car p) 'ec%@) ;;遇到了落单的 ,@ 。它不可能单独出现
(error "dotted ,@~S" p))))))
(defun ec-bracket (x)
(cond
((atom x) ;; 给原子加上 quote
(list 'list (ec-bk-process x)))
((eq (car x) 'ec%) ;; 对 (unquote expr) 的处理
(list 'list (cadr x)))
((eq (car x) 'ec%@) ;; 对 (unquote-splicing expr) 的处理
(cadr x))
(t ;; 其他情况,也就是规则中的“进一步处理”
(list 'list (ec-bk-process x)))))
我们可以用上面的例子来检验一下正确性,由于没有用 read-macro,所以写起来有点麻烦:
(ec$ (1 (ec% (+ 2 3))))
(1 5)
(ec$ ((ec%@ (list 1 2 3))))
(1 2 3)
(ec$ (ec$ ((ec% (ec% (list '+ 1 2))))))
(APPEND (LIST (+ 1 2)) 'NIL)
(3)
(ec$ (ec$ ((ec% (quote (ec% (list '+ 1 2)))))))
(APPEND (LIST '(+ 1 2)) 'NIL)
((+ 1 2))
(ec$ (ec$ ((ec%@ (ec% (list 'list 1 2 3))))))
(APPEND (LIST 1 2 3) 'NIL)
(1 2 3)
(ec$ (ec$ ((ec%@ (quote (ec% (list 'list 1 2 3)))))))
(APPEND '(LIST 1 2 3) 'NIL)
(LIST 1 2 3)
(ec$ (ec$ ((ec% (ec%@ (list '(+ 1 2) '(+ 2 3)))))))
(APPEND (LIST (+ 1 2) (+ 2 3)) 'NIL)
(3 5)
(ec$ (ec$ ((ec% (quote (ec%@ (list '(+ 1 2))))))))
(APPEND (LIST '(+ 1 2)) 'NIL)
((+ 1 2))
(ec$ (ec$ ((ec%@ (ec%@ (list '(list 1 2) '(list 2 3)))))))
(APPEND (LIST 1 2) (LIST 2 3) 'NIL)
(1 2 2 3)
(ec$ (ec$ ((ec%@ (ec%@ (list '(list 1 2)))))))
(APPEND (LIST 1 2) 'NIL)
(1 2)
本想着也简化一下 elisp 中的实现贴过来的,不过这一节的内容已经够多了,关于 elisp 我们留到下一贴吧。