【问题标题】:Eval in Function Versus Macro in Clojure函数中的 eval 与 Clojure 中的宏
【发布时间】:2014-04-26 18:42:48
【问题描述】:

考虑以下函数

(defn shove [data fun] (eval `(-> ~data ~fun)))

这里按预期工作

(shove [1 2 3] count)  ;; ~~> 3

甚至在这里,它预计会失败,因为它评估(count) 太早了

(shove [1 2 3] (count))
;; ~~> clojure.lang.Compiler$CompilerException: clojure.lang.ArityException: 
;;       Wrong number of args (0) passed to: core$count, compiling:(null:5:1)

但是在这里,当我定义一个显式的 form 并将其作为数据传递给函数时,一切都很好:

(def move '(count))
(shove [1 2 3] move)  ;; ~~> 3

现在,为了摆脱对eval 的显式调用,我尝试

(defmacro shovem [data form] `(-> ~data ~form))

效果很好

(shovem [1 2 3] count)    ;; ~~> 3
(shovem [1 2 3] (count))  ;; ~~> 3

但它现在在显式定义的表单 move 上意外失败,并出现错误提示它评估 move 以获取 (count),然后继续尝试评估 (count),但方式与以前不同.

(shovem [1 2 3] move)  
;; ~~> java.lang.ClassCastException: 
;; clojure.lang.PersistentList cannot be cast to clojure.lang.IFn

我对这个错误消息感到困惑,我不知道如何获得所需的行为,即shovem 应该适用于所有三种输入,像count 这样的裸函数,像这样的括号函数形式(count),以及像 move 这样评估为此类形式的数据对象。

我可以在函数版本中使用eval,但是,此时我意识到我不明白发生了什么,我想完成练习以提高我的理解。

【问题讨论】:

  • 尝试macroexpand 看看你的宏调用实际生成了什么

标签: macros clojure


【解决方案1】:

解决方案?

在最一般的情况下,您需要一个宏和eval 来完成本练习,我认为这是为了学习(请不要这样做)。

为了举例,保持 shove 原样,并用作修改后的 shovem 的助手

(defn shove [x form] (eval `(-> ~x ~form)))

(defmacro shovem* [x form] 
  (if (seq? form) 
    (if (= 'quote (first form))
      `(-> ~x ~(second form))
      `(-> ~x ~form)) 
    `(shove ~x ~form)))

现在,shovem* 具有您正在寻找的语义

(def move '(count))

(shovem* [1 2 3] count) ;=> 3
(shovem* [1 2 3] (count)) ;=> 3
(shovem* [1 2 3] '(count)) ;=> 3
(shovem* [1 2 3] move) ;=> 3
(let [f count, d [1 2 3]] (shovem* d f)) ;=> 3

原始宏的问题(?)

user=> (def move '(count))
user=> (defmacro shovem [data form] `(-> ~data ~form))

user=> (macroexpand-1 '(shovem [1 2 3] move))
(clojure.core/-> [1 2 3] move)

user=> (macroexpand-1 '(clojure.core/-> [1 2 3] move)) 
(move [1 2 3])

user=> (macroexpand-1 '(move [1 2 3]))
(move [1 2 3]) ; same, move is not a macro

这样就结束了宏扩展阶段。现在(move [1 2 3]) 是代码。评估时会发生什么?

user=> (move [1 2 3])
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn 

如果原因不明显,您需要重新考虑evaluation rules(move [1 2 3]) 的形式是一个列表,move 不是特殊形式或宏。所以这被认为是对其参数[1 2 3]move 的函数调用。但是move是什么?

user=> (type move)
clojure.lang.PersistentList

user=> (ifn? move)
false

所以,move 不是一个函数,也不知道如何表现得像一个函数。这只是一个列表。

user=> (= move (list 'count))
true    

【讨论】:

    【解决方案2】:

    在您的代码中,shovem 被传递给 move 符号,而不是 move 的实际值,因为它是一个宏。因此,您对shovem 的调用将扩展为:

    (-> [1 2 3] move)
    

    -> 是另一个将move 隐式包装在列表中的宏,因为它是一个符号,所以这段代码相当于:

    (-> [1 2 3] (move))
    

    这就是为什么->完全展开后,就变成了

    (move [1 2 3])
    

    move 是一个序列,而不是一个函数,因此java.lang.ClassCastException

    我不确定您使宏对所有输入起作用的目标是否可行,因为作为在任何代码执行之前运行的宏,它不知道传递的 move 符号是否应该是评估(得到(计数))或仅按字面意思使用。通常,宏仅根据传递给它的参数的形式,而不是根据它们的运行时值,知道是否应该计算每个参数或计算多少次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-09
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 2011-12-12
      • 2014-08-09
      • 2011-07-28
      相关资源
      最近更新 更多