在Clojure中的功能与宏的评估

时间:2014-04-26 18:42:49

标签: macros clojure

考虑以下功能

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

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

(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,但是,在这一点上,我意识到我不明白发生了什么,我想完成练习以提高我的理解。 / p>

2 个答案:

答案 0 :(得分:4)

解决方案?

在最常见的情况下,为了这个练习的目的,你需要一个宏和eval,我认为这是为了学习(请不要实际做< / em> this)。

为了举例,请按原样保留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不是特殊形式或宏。因此,这被认为是move在其参数[1 2 3]上的函数调用。但是什么是move

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

user=> (ifn? move)
false

因此,move不是一个功能,也不知道如何像一个人那样行事。它只是一个清单。

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

答案 1 :(得分:3)

在您的代码中,shovem传递move符号,而不是移动的实际值,因为它是一个宏。因此,您对shovem的电话会扩展为:

(-> [1 2 3] move)

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

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

这就是为什么在->完全展开后,它变为

(move [1 2 3])

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

我不确定你让宏为所有输入工作的目标是可行的,因为它是一个在任何代码执行之前运行的宏,它不知道传递的move符号是否是应该评估(得到(计数))或只是字面意思。通常,宏只知道每个参数应该被评估,或者根据传递给它的参数的形式,而不是它们的运行时值来知道。