在clojure中,如何将宏应用于列表?

vie*_*bel 27 clojure

clojure,apply不能应用于宏.例如,(apply and [true false])引发异常.我正在考虑以下解决方法:

(defmacro apply-macro[func args] `(~func ~@args))
Run Code Online (Sandbox Code Playgroud)

乍一看,它看起来效果很好:

(apply-macro and [true 5]); 5
(apply-macro and [true 5 0]); 0
(let [a 0] (apply-macro and [true a])); 0
Run Code Online (Sandbox Code Playgroud)

但是,当我向它传递一个指向向量的变量时,它就崩溃了.

(let [a [true]] (apply-macro and a));  java.lang.IllegalArgumentException:
   ;Don't know how to create ISeq from: clojure.lang.Symbol
Run Code Online (Sandbox Code Playgroud)

多么令人失望!!!!

知道怎么解决apply-macro

Ale*_*art 30

你没有.

宏在评估/编译期间而不是在运行时扩展,因此它们可以使用的唯一信息是传入的args,而不是args在运行时评估的内容.这就是文字向量起作用的原因,因为文字向量在编译时存在,但a只是一个符号; 它只会在运行时评估为向量.

要使and列表具有类似行为,请使用(every? identity coll).

要使or列表具有类似行为,请使用(some identity coll).


mik*_*era 28

问题是它a只是编译时的符号.因此,编译时宏无法查看它包含的内容并进行必要的扩展.因此,您需要使用eval在运行时扩展宏.

一种方法是将宏包装在一个调用eval的函数中,这可以通过这个方便的"函数化"宏来完成:

(defmacro functionize [macro]
  `(fn [& args#] (eval (cons '~macro args#))))

(let [a [true]] (apply (functionize and) a))
=> true
Run Code Online (Sandbox Code Playgroud)

如果您愿意,还可以根据函数化定义apply-macro:

(defmacro apply-macro [macro args]
   `(apply (functionize ~macro) ~args))

(let [a [true false]] (apply-macro and a))
=> false
Run Code Online (Sandbox Code Playgroud)

说完这一切之后,我仍然认为最好的办法就是在不需要宏时完全避免使用宏:它们会增加额外的复杂性,最好保留用于真正需要编译时代码生成的情况.在这种情况下,你不会:Alex Taggart的答案提供了一个很好的例子,说明如何在没有任何宏的情况下实现类似的目标,这在大多数情况下可能更合适.

  • 作为一个社区,我们应该尽量避免鼓励没有经验的人过度使用宏,特别是"eval",特别是当有更好的工具时. (3认同)