函数评估与Clojure中的宏

Reb*_*bin 2 macros clojure

考虑以下功能

(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在函数版本中使用它,但是,到现在为止,我意识到我不了解发生了什么,我想完成练习以增进理解。

A. *_*ebb 5

一个办法?

在最一般的情况下,您需要一个宏,并且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)