考虑以下功能
(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>
答案 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
符号是否是应该评估(得到(计数))或只是字面意思。通常,宏只知道每个参数应该被评估,或者根据传递给它的参数的形式,而不是它们的运行时值来知道。