【问题标题】:Simple macro doesn't seem to execute given forms简单的宏似乎没有执行给定的表单
【发布时间】:2016-11-11 01:08:25
【问题描述】:

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

(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)))

我已经对此进行了测试,并在其他宏中使用了它,所以我知道它可以正常工作。

我的问题可以用下面的sn-p来描述:

(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)))

但对我来说没有什么特别突出的。

这是怎么回事?

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

【问题讨论】:

  • 你想让我把另一个链接到这个吗?
  • @ArthurUlfeldt 好吧​​,我删除了它,所以只有非常高级的用户才能看到它。除了使用的代码之外,它基本上是相同的。回想起来,我可能应该刚刚编辑了原件。我不知道为什么我提出了一个新问题。

标签: clojure macros


【解决方案1】:

解决方案

我认为这里发生的情况是您的 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 将sexps 视为列表,而此时time-pure 想要执行它们,它只是将ret# 分配给列表的值。所以这个列表需要evaled。

不过可能有更好的方法来实现这一点。

【讨论】:

  • 为什么在这种情况下需要evaltime-pure 在直接使用时可以正常工作?我想我明天早上需要再考虑一下。宏正在腐蚀我的大脑:/
  • 带有 -1 的礼貌评论解释了原因。
  • 我知道为什么你被否决了。也许eval 的使用触发了某人?我会赞成抵消,因为我真的很想了解这里发生了什么,这是一个不同的看法。
  • 喜欢自动反应!无论如何,如果eval 是答案,这可能意味着应该在编译时评估的东西已经转移到运行时。和平! :)
  • 我注意到一些巨魔经常对不被认为“足够纯粹”的答案投反对票。即使他们只使用基本功能!
【解决方案2】:
(defmacro time-each [& exprs]
  `(list ~@(for [expr exprs]
             `(time-pure ~expr))))

您需要注意不要评估 time-pure 上下文之外的表达式,就像 Arthur 的回答中所做的那样,它评估每个表达式,然后在“查看结果”。而是在计算每个表达式之前将 time-pure 包裹起来。

【讨论】:

  • 是什么阻止了表达式在被提供给time-pure 之前被评估?抱歉,我仍在努力避免被宏所困扰。
  • 我注意到你的一些宏观答案,你使用for而不是map。我有什么特别的原因,还是只是个人风格?
  • 似乎是最干净和最直接的。谢谢。
  • 我想我在这里遗漏了一些上下文。 “亚瑟的答案”在哪里?
  • @pdoherty926,我相信亚瑟的回答由于某种原因被删除了。
【解决方案3】:

“我仍在努力不要把我的头放在宏上。”

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

(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))))

令人震惊的是 time-pure 正在传递一个带引号的列表,所以它只是 运行时的符号,而不是宏扩展时。

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

我已经修改了 time-pure 以打印出它的宏扩展时间参数 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# 被绑定到该符号表达式而不是预期的计算。

再次,希望打印第一个的类型可以清楚地表明 time-pure 正在运行时处理符号列表。

对不起,如果这一切令人困惑,但道理很简单:当宏弄乱你的头脑时,使用 macroexpand-1 和嵌入的打印语句。随着时间的推移,您将内化两个不同的时间,即宏扩展和运行。

【讨论】:

    猜你喜欢
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 2018-08-19
    • 2015-01-20
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多