Shm*_*ger 3 macros performance clojure apply
其中clojure.core有一个apply函数,以多重数量风格编写(大概是为了效率)。
但是我问是否有这样的实现:
(defmacro apply [f coll] (conj (seq coll) f))
不会更好。
我知道宏的效率低于函数,但它真的有这么大的区别吗?
或者,是否不可能将此宏编写为函数(我尝试过但失败了,但这并不能证明,因为我没有编写宏的经验)?
使用函数而不是宏的原因apply是它必须如此。您定义的宏版本是无用的:它不起作用。
但为什么不呢?这似乎是一个合理的想法。让我们尝试一下,但调用它mapply以便我们仍然可以引用现有的apply:
=> (defmacro mapply [f coll] (conj (seq coll) f))
Run Code Online (Sandbox Code Playgroud)
我打赌你做到了这一点,而且我打赌你甚至测试过它:你可以在绝对最简单的情况下使用mapply,如下所示:
=> (macroexpand '(mapply + [1 2 3]))
(+ 1 2 3)
=> (mapply + [1 2 3])
6
Run Code Online (Sandbox Code Playgroud)
问题是这没有用:你写了(mapply + [1 2 3]),但写起来也同样容易(实际上更容易)(+ 1 2 3)。所以这个用例并不是mapply有用的证据。还有什么时候apply-like 宏会有用?
的真正价值apply在于,您可以将其应用于在运行时确定的列表:当参数列表在编译时固定时,您不需要,apply因为您可以直接编写(f x y)而不是(apply f [x y]).
因此,让我们在运行时计算的列表上尝试您的版本:
=> (apply + (range 4))
6
=> (macroexpand '(mapply + (range 4)))
(+ range 4)
=> (mapply + (range 4))
Execution error (ClassCastException) at user/eval2051 (REPL:1).
clojure.core$range cannot be cast to java.lang.Number
Run Code Online (Sandbox Code Playgroud)
发生了什么?你mapply看到那(range 4)是一个集合,所以它放在+它的前面。您想要的是评估(range 4),生成一个列表,然后以某种方式添加其元素。但因为mapply在编译时工作,所以它看到的列表是(range 4):一个包含元素range和 4 的二元素列表。
这不仅仅是mapply定义中的一些简单错误:不可能定义mapply为宏(除非是一个在编译时不起作用并apply在运行时委托给的微不足道的宏)。对于一个更明显不可能的示例,请考虑mapply从函数内部调用您的:
=> (defn sum [xs] (apply + xs))
=> (sum (range 4))
6
=> (defn msum [xs] (mapply + xs))
Syntax error macroexpanding mapply at (REPL:1:17).
Don't know how to create ISeq from: clojure.lang.Symbol
Run Code Online (Sandbox Code Playgroud)
既然mapply是一个宏,它当然会在编译时运行,即在我们定义时运行msum。它只有一次扩展的机会,不知道xs我们将来会通过它做什么。因此,当它查看 时xs,它只看到符号xs,而不是某个列表。结果,seq调用不可避免地失败。再说一遍,没有其他实现可以工作,因为您在编译时根本不知道列表有多少元素,并且无法扩展到正确的调用。
作为最后的对待,考虑另一个apply不能是宏的原因:您可以将函数应用于无限的参数列表,但如果您尝试生成无限长的宏扩展,编译器将度过一段非常悲伤的时光。
=> (defn apply-to-nats [f] (apply f (range)))
=> (apply-to-nats (fn [& args] (first args)))
0
=> (defmacro mapply-to-nats [f] (cons f (range)))
=> (mapply-to-nats (fn [& args] (first args)))
Syntax error (OutOfMemoryError) compiling at (REPL:1:1).
GC overhead limit exceeded
Run Code Online (Sandbox Code Playgroud)
发生了什么?编译器循环运行,直到我的系统内存不足,尝试编译
((fn [& args] (first args)) 0 1 2 3 4 ...)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
88 次 |
| 最近记录: |