在Elisp

时间:2016-03-24 07:58:32

标签: macros elisp

我总是对Emacs中的macro感到困惑。有很多关于如何使用macro的文档。但是文件只提到表面而且例子往往过于简单。另外,搜索macro本身非常困难。它将提出键盘宏结果。

人们总是说宏在编译时被扩展,并且它与直接编写相同的代码副本一样快。这总是如此吗?

我从示例when开始。

(pp-macroexpand-expression
 '(when t
    (print "t")))

;; (if t
;;     (progn
;;       (print "t")))

当我们在编译期间使用when时,(if t .....)会直接插入到我们的代码中,对吗?

然后,我从dotimes找到了一个更复杂的示例subr.el。我稍微简化了代码:

(defmacro dotimes (spec &rest body)

  (declare (indent 1) (debug dolist))

  (let ((temp '--dotimes-limit--)
        (start 0)
        (end (nth 1 spec)))

    `(let ((,temp ,end)
           (,(car spec) ,start))
       (while (< ,(car spec) ,temp)
         ,@body
         (setq ,(car spec) (1+ ,(car spec))))
       ,@(cdr (cdr spec)))))

引起我注意的是(end (nth 1 spec))。我认为这部分必须在运行时完成。在运行时完成此操作时,意味着无法在编译时完成代码扩展。 为了测试它,我修改了dotimes并对文件进行字节编译。

(defmacro my-dotimes (spec &rest body)
  (declare (indent 1) (debug dolist))
  (let ((temp '--dotimes-limit--)
        (start 0)
        ;; This is my test
        (end (and (print "test: ")(print (nth 1 spec)) (nth 1 spec))))
    `(let ((,temp ,end)
           (,(car spec) ,start))
       (while (< ,(car spec) ,temp)
         ,@body
         (setq ,(car spec) (1+ ,(car spec))))
       ,@(cdr (cdr spec)))))

(provide 'my)

结果

(require 'my)
(my-dotimes (var 3)
  (print "dotimes"))

;; "test: "
;; 3
;; "dotimes"
;; "dotimes"
;; "dotimes"

确实,我的测试语句是在运行时完成的。 谈到扩张:

(pp-macroexpand-expression
 '(my-dotimes (var 3)
    (print "dotimes")))

;; (let
;;     ((--dotimes-limit-- 3)
;;      (var 0))
;;   (while
;;       (< var --dotimes-limit--)
;;     (print "dotimes")
;;     (setq var
;;           (1+ var))))

令人惊讶的是我的测试部分丢失了。

dotimes在运行时是否已扩展?

如果是,是否意味着它失去了通用macro的优势,macro与直接编写相同的代码一样快?

解释器遇到具有运行时组件的macro时会做什么?

2 个答案:

答案 0 :(得分:1)

我的印象是您对包含宏定义的文件进行了字节编译,但是没有对调用宏的文件进行字节编译?

宏扩展的调用

如果编译调用代码,则在编译时进行扩展;在加载时(&#34;渴望&#34;宏扩展,仅在最近的Emacs版本中)如果加载的代码没有编译;如果急剧扩张不可能或不可用,则在运行时。

显然,如果在运行时评估对宏的调用,则无法提前扩展它,因此在运行时立即进行扩展。

编译时(或加载时)的宏扩展是可能的,因为宏参数评估。在扩展时评估(nth 1 spec)没有任何问题,因为spec的值是未评估的参数,该参数出现在对宏的原始调用中。

即。展开(dotimes (var 3))时,spec参数为列表(var 3),因此(nth 1 spec)在扩展时评估为3

为了清晰起见(因为数字可能会使问题混淆),如果它是(nth 0 spec),那么它将评估为符号var,特别是 {{ 1}}&#39; 值作为变量(在扩展时无法建立)。

所以var - 实际上对传递给宏的未评估参数的任何操作 - 都可以在扩展时建立绝对

(编辑:等等,我们在Can I put condition in emacs lisp macro?

中介绍了这一点

如果你问如果宏在扩展时在某些情况下在运行时动态动态时会发生什么,答案就是它看到了扩展时间值,并且因此,扩展代码可能会根据扩展的时间而变化。没有什么可以阻止你编写以这种方式运行的宏,但它通常是不可取的。

答案 1 :(得分:0)

只是我的两分钱......

  

人们总是说宏在编译时被扩展,并且它与直接编写相同的代码副本一样快。这总是如此吗?

并非总是如此。

如果你编译一个代码包含对宏的调用的函数,那就是真的。

但是,如果你不编译它,那几乎是真的。

假设您使用修改后的dotimes版本定义了一个函数,例如:

(defun foo (x)
  (my-dotimes (var x)
    (print "dotimes")))

编译它。

如果你只调用一次(foo 5),那么解释器必须在评估扩展代码之前扩展宏,这样你就会看到打印件,它会比你自己编写扩展代码的速度慢。功能

但是现在,my-times的定义中不再出现符号foo。包含它的列表已被其扩展的结果所取代。

所以,如果你再次呼叫foo,就像(foo 3)一样,现在你再也看不到打印件了,它会像你自己编写扩展代码一样快。