在clojure
中,apply
无法应用于宏。例如(apply and [true false])
引发异常。我正在考虑采取以下解决方法:
(defmacro apply-macro[func args] `(~func ~@args))
乍一看,它看起来效果很好:
(apply-macro and [true 5]); 5
(apply-macro and [true 5 0]); 0
(let [a 0] (apply-macro and [true a])); 0
但是,当我向它传递一个指向矢量的变量时,它就会崩溃。
(let [a [true]] (apply-macro and a)); java.lang.IllegalArgumentException:
;Don't know how to create ISeq from: clojure.lang.Symbol
多么令人失望!!!!
知道如何修复apply-macro
?
答案 0 :(得分:30)
你没有。
宏在评估/编译期间扩展,而不是在运行时,因此他们可以使用的唯一信息是传入的参数,而不是args在运行时评估的内容。这就是文字向量的工作原理,因为文字向量在编译时存在,但a
只是一个符号;它只会在运行时评估为矢量。
要获得与and
类似的列表行为,请使用(every? identity coll)
。
要获得与or
类似的列表行为,请使用(some identity coll)
。
答案 1 :(得分:28)
问题是a
只是编译时的符号。因此,编译时宏无法查看它包含的内容并进行必要的扩展。因此,您需要使用eval在运行时展开宏。
这样做的一种方法就是将宏包装在一个调用eval的函数中,这可以通过这个方便的“函数化”宏来完成:
(defmacro functionize [macro]
`(fn [& args#] (eval (cons '~macro args#))))
(let [a [true]] (apply (functionize and) a))
=> true
如果您愿意,还可以根据functionize定义apply-macro:
(defmacro apply-macro [macro args]
`(apply (functionize ~macro) ~args))
(let [a [true false]] (apply-macro and a))
=> false
说完这一切之后,我仍然认为最好的办法就是在不需要宏时完全避免使用宏:它们会增加额外的复杂性,最好保留用于真正需要编译时代码生成的情况。在这种情况下,你不会:Alex Taggart's answer给出一个很好的例子,说明如何在没有任何宏的情况下实现类似的目标,这在大多数情况下可能更合适。
答案 2 :(得分:1)
当然,正确的答案是不要这样做。但是,因为我无法抵抗一个好的黑客:
(defmacro apply-macro
"Applies macro to the argument list formed by prepending intervening
arguments to args."
{:arglists '([macro args]
[macro x args]
[macro x y args]
[macro x y z args]
[macro a b c d & args])}
[macro & args+rest]
(let [args (butlast args+rest)
rest-args (eval (last args+rest))]
`(eval
(apply (deref (var ~macro))
'(~macro ~@args ~@rest-args)
nil
~@(map #(list 'quote %) args)
'~rest-args))))
用法:
hackery> (->> (range 5) rest rest rest rest)
(4)
hackery> (apply-macro ->> (range 5) (repeat 4 'rest))
(4)
资格:
&env
参数的宏。答案 3 :(得分:0)
如果参数列表可以具有无限长度,则此方法不起作用,但如果您只需要应用长度为n
的列表,则可以创建包装函数与n
arities:
user> (defmacro foo [& rest] `(println ~@rest))
#'user/foo
user> (apply foo [1 2])
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'user/foo, compiling:(*cider-repl repo*:865:7)
user> (defn foo-up-to-ten-args
([a] (foo a))
([a b] (foo a b))
([a b c] (foo a b c))
([a b c d] (foo a b c d))
([a b c d e] (foo a b c d e))
([a b c d e f] (foo a b c d e f))
([a b c d e f g] (foo a b c d e f g))
([a b c d e f g h] (foo a b c d e f g h))
([a b c d e f g h i] (foo a b c d e f g h i))
([a b c d e f g h i j] (foo a b c d e f g h i j)))
#'user/foo-up-to-ten-args
user> (apply foo-up-to-ten-args [1 2])
1 2
nil
user> (apply foo-up-to-ten-args (range 0 10))
0 1 2 3 4 5 6 7 8 9
nil
user> (apply foo-up-to-ten-args (range 0 11))
ArityException Wrong number of args (11) passed to: user/foo-up-to-ten-args clojure.lang.AFn.throwArity (AFn.java:429)
就我而言,如果没有eval
,我就可以做到这一点。
答案 4 :(得分:0)
将args应用于条件宏,例如or
,case
,cond
&例如condp
。您可以使用some
,every
& partition
功能。在这些示例中省略了单个else子句,但可以相当容易地添加
;apply to the 'or' macro
(some identity [nil false 1 2 3])
=> 1
;apply to the 'case' macro.
(some
(fn [[case value]]
(and (= case 2) value))
(partition 2 [1 "one" 2 "two" 3 "three"]))
=> "two"
;apply to the 'cond' macro
(some
(fn [[case value]]
(and case value))
(partition 2 [false "one" true "two" false "three" :else "four"]))
=> "two"
;apply to the 'condp' macro
(let [[f v & args] [= 2 1 "one" 2 "two" 3 "three"]]
(some
(fn [[case value]]
(and (f case v) value))
(partition 2 args)))
every?
可用于and
宏
;apply to the 'and' macro
(every? identity [true true true])
=> true