简单的宏似乎不执行给定的形式

时间:2016-11-11 01:08:26

标签: clojure macros

我使用这个基本宏作为其他计时宏的构建块:

(defmacro time-pure
  "Evaluates expr and returns the time it took.
  Modified the native time macro to return the time taken."
  [expr]
  `(let [start# (current-nano-timestamp)
         ret# ~expr]
     (/ (double (- (current-nano-timestamp) start#)) 1000000.0)))

我已对此进行过测试,并在其他宏中使用它,所以我知道它运行正常。

我的问题可以通过以下代码段来描述:

(defmacro time-each [& exprs]
  `(mapv #(time-pure %) '~exprs))

我希望这会将每个表达式赋予time-each,在其中执行并计时;返回结果。然而,当我测试它时,它立即完成:

(time-each
  (Thread/sleep 500)
  (Thread/sleep 1000))

[0.036571 0.0]

我对此感到困惑,因为我知道(time-pure (Thread/sleep 1000))将花费大约一秒的时间来返回,并且所有这个宏都会委托给time-pure

造成这种情况的原因是什么?我真的不知道如何正确调试宏。我使用macro-expand-1来检查生成的代码:

(clojure.pprint/pprint
  (macroexpand-1
    '(time-each
      (Thread/sleep 500)
      (Thread/sleep 1000))))

(clojure.core/mapv
 (fn*
  [p1__1451__1452__auto__]
  (helpers.general-helpers/time-pure p1__1451__1452__auto__))
 '((Thread/sleep 500) (Thread/sleep 1000)))

但没有什么能让我真正脱颖而出。

这里发生了什么?

(注意,这是我几分钟前发布的一个问题。我意识到我展示的案例很复杂,所以我做了一个更好的例子。)

3 个答案:

答案 0 :(得分:2)

<强>解决方案

我认为这里发生的是mapv在编译时执行,这意味着在运行时,这就是为什么它不能将代码列表“粘贴”为sexp。我认为更好的方法是将mapv保留在语法引用之外:

(defmacro each-time-mine [& exprs]
  (mapv #(time-pure %) exprs))


(each-time-mine
  (Thread/sleep 500)
  (Thread/sleep 1000))

[501.580465 1001.196752]

<强>原始

虽然eval通常不受欢迎,但在这种情况下似乎可以解决问题:

(defmacro time-pure
  [expr]
  `(let [start# (current-nano-timestamp)
         ret# (eval ~expr)]
     (/ (double (- (current-nano-timestamp) start#)) 1000000.0)))

(defmacro time-each [& exprs]
  `(mapv #(time-pure %) '~exprs))

(time-each
  (Thread/sleep 500)
  (Thread/sleep 1000))

[501.249249 1001.242522]

mapv会将性别视为列表,并且time-pure想要执行它们时,它只会为列表的值分配ret#。所以该列表需要eval

虽然可能有更好的方法来实现这一目标。

答案 1 :(得分:1)

(defmacro time-each [& exprs]
  `(list ~@(for [expr exprs]
             `(time-pure ~expr))))

你需要注意不要评估time-pure上下文之外的表达式,就像在Arthur的答案中所做的那样,它会评估每个表达式,然后在(非常)上调用time-pure快速运行&#34;查看结果&#34;。相反,在评估它之前,在每个表达式周围包裹time-pure

答案 2 :(得分:0)

“我仍然试图不要围绕宏。”

需要一段时间。 :)帮助我的一件事是宏观扩张:

(macroexpand-1  '(time-each (+ 2 2 )(+ 3 3)))

产生:

(clojure.core/mapv
    (fn* [p1__24013__24014__auto__] 
        (rieclj.core/time-pure p1__24013__24014__auto__))
    (quote ((+ 2 2) (+ 3 3))))

令人震惊的是,时间纯粹是通过引用列表,所以它只是运行时的符号,而不是宏扩展时间。

然后另一件帮助我的事情是Lispy宏的美妙之处在于它们是在不同时间运行的正常函数(当读者正在处理源代码时)。但是因为它们是正常函数,所以我们可以使用print语句探测它们,就像我们探测普通代码一样。

我修改了time-pure来打印出它的macroexpansion-time参数expr,并在生成代码中打印出已评估的输入,我们现在怀疑这是一个符号列表,或'(+ 2 2) )在第一种情况下。

(defmacro time-pure
  "Evaluates expr and returns the time it took.
   Modified the native time macro to return the time taken."
  [expr]
 (prn :time-pure-sees expr)
 `(let [start# (now)
        ret# ~expr]
    (prn :ret-is ret# (type (first  ret#)))
    (/ (double (- (now) start#)) 1000000.0)))

我打印第一个ret的类型以驱动回家+是符号,而不是函数。评价:

(time-each (+ 2 2 )(+ 3 3))

的产率:

:time-pure-sees p1__24013__24014__auto__
:ret-is (+ 2 2) clojure.lang.Symbol
:ret-is (+ 3 3) clojure.lang.Symbol

看到(+ 2 2)可能会让你觉得一切都很好,但关键是右边是'(+ 2 2)所以ret#绑定到那个符号表达而不是预期的计算。

同样,打印第一种类型希望能够清楚地表明,时间纯粹是在运行时处理符号列表。

很抱歉,如果所有这些都令人困惑,但道德很简单:当宏与你的脑袋混乱时,使用macroexpand-1和嵌入式打印语句。随着时间的推移,你将内化两个不同的时间,宏扩展和运行。