考虑以下功能
(defn shove [data fun] (eval `(-> ~data ~fun)))
Run Code Online (Sandbox Code Playgroud)
在这里按预期工作
(shove [1 2 3] count) ;; ~~> 3
Run Code Online (Sandbox Code Playgroud)
甚至在这里,它预计会失败,因为它评估(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)
Run Code Online (Sandbox Code Playgroud)
但是在这里,当我定义一个显式形式并将其作为数据传递给函数时,一切都很好:
(def move '(count))
(shove [1 2 3] move) ;; ~~> 3
Run Code Online (Sandbox Code Playgroud)
现在,为了摆脱对的显式调用eval,我尝试
(defmacro shovem [data form] `(-> ~data ~form))
Run Code Online (Sandbox Code Playgroud)
哪个很好
(shovem [1 2 3] count) ;; ~~> 3
(shovem [1 2 3] (count)) ;; ~~> 3
Run Code Online (Sandbox Code Playgroud)
但现在意外失败的明确定义的形式move,与表明,它计算错误move获得(count),然后不停地尝试评估(count),但比以前的方式不同。
(shovem [1 2 3] move)
;; ~~> java.lang.ClassCastException:
;; clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
Run Code Online (Sandbox Code Playgroud)
我对此错误消息感到困惑,而且我不知道如何获得所需的行为,这shovem应该适用于所有三种输入,裸函数(如)count,括号函数形式(如(count))和数据对象move(如此类)形式。
我可以eval在函数版本中使用它,但是,到现在为止,我意识到我不了解发生了什么,我想完成练习以增进理解。
在最一般的情况下,您需要一个宏,并且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)))
Run Code Online (Sandbox Code Playgroud)
现在,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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
宏扩展阶段到此结束。现在(move [1 2 3])是代码。被评估时会发生什么?
user=> (move [1 2 3])
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
Run Code Online (Sandbox Code Playgroud)
如果原因不明显,则需要重新考虑评估规则。该表单(move [1 2 3])是一个列表,move不是特殊形式或宏。因此,这被视为move对其参数的函数调用[1 2 3]。但是什么move呢?
user=> (type move)
clojure.lang.PersistentList
user=> (ifn? move)
false
Run Code Online (Sandbox Code Playgroud)
因此,move它不是一个函数,也不知道如何像一个函数一样工作。这只是一个清单。
user=> (= move (list 'count))
true
Run Code Online (Sandbox Code Playgroud)