Macros 函数中的求值与Clojure中的宏
考虑以下函数Macros 函数中的求值与Clojure中的宏,macros,clojure,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: ;;
(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
我对这个错误消息感到困惑,我不知道如何获得所需的行为,即spoom
应该可以处理所有三种类型的输入、裸函数(如count
)和括号函数(如(count)
)以及数据对象(如move
),这些数据对象的计算结果都是这样的
我可以在函数版本中使用eval
,但此时,我意识到我不明白发生了什么,我想完成练习以提高我的理解。解决方案?
在最一般的情况下,为了本练习的目的,您需要一个宏和eval
,我假设这是为了学习(请不要实际这样做)
例如,保持spoof
原样,并将其用作修改后的spoom
(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)))
现在,spowm*
具有您想要的语义
(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
原始宏的问题(?) 这就结束了宏扩展阶段。现在
(移动[1 2 3])
就是代码。评估时会发生什么
user=> (move [1 2 3])
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
如果原因不明显,你需要重新考虑。表单(move[1 2 3])
是一个列表,而move
不是特殊的表单或宏。因此,这被认为是对其参数[1 2 3]
的move
函数调用。但是什么是移动
user=> (type move)
clojure.lang.PersistentList
user=> (ifn? move)
false
所以,move
不是一个函数,也不知道如何像函数一样工作。这只是一张单子
user=> (= move (list 'count))
true
在代码中,
spowm
传递符号,而不是move的实际值,因为它是一个宏。因此,对spowm
的调用将扩展到:
(-> [1 2 3] move)
->
是另一个隐式将移动
包装在列表中的宏,因为它是一个符号,所以此代码相当于:
(-> [1 2 3] (move))
这就是为什么在->
完全展开后,它会变为
(move [1 2 3])
而move
是一个序列,而不是一个函数,因此java.lang.ClassCastException
我不确定让宏对所有输入都工作的目标是否可行,因为作为一个在任何代码执行之前运行的宏,它不知道传递的
move
符号是应该评估的(以获取(计数))还是只是字面上使用。一般来说,宏知道是否应该对每个参数求值或求值次数,只基于传递给它的参数的形式,而不是它们的运行时值。尝试macroexpand
查看宏调用实际生成的内容
(move [1 2 3])