Macros 在&;中收集的序列上迭代Clojure变量宏;额外参数
问题:当要传递的参数是序列,并且catch all变量需要作为序列处理时,如何在宏中的Macros 在&;中收集的序列上迭代Clojure变量宏;额外参数,macros,clojure,Macros,Clojure,问题:当要传递的参数是序列,并且catch all变量需要作为序列处理时,如何在宏中的&之后处理catch all参数?catch all变量中列出的是文字表达式 这是一个宏,旨在大致表现Common Lisp的mapc,即执行Clojure的map所做的操作,但仅用于副作用,且无惰性: (defmacro domap [f & colls] `(dotimes [i# (apply min (map count '(~@colls)))] (apply
&
之后处理catch all参数?catch all变量中列出的是文字表达式
这是一个宏,旨在大致表现Common Lisp的mapc
,即执行Clojure的map
所做的操作,但仅用于副作用,且无惰性:
(defmacro domap [f & colls]
`(dotimes [i# (apply min (map count '(~@colls)))]
(apply ~f (map #(nth % i#) '(~@colls)))))
我逐渐意识到这不是一种编写domap
的好方法——我得到了关于这个问题的好建议。然而,我仍然对我在过程中遇到的棘手的宏观问题感到疑惑
如果集合作为文本传递,则此操作有效:
user=> (domap println [0 1 2])
0
1
2
nil
但在其他类似情况下不起作用:
user=> (domap println (range 3))
range
3
nil
user=> (def nums [0 1 2])
#'user/nums
user=> (domap println nums)
UnsupportedOperationException count not supported on this type: Symbol clojure.lang.RT.countFro (RT.java:556)
或者这个:
user=> (domap println (range 3))
range
3
nil
user=> (def nums [0 1 2])
#'user/nums
user=> (domap println nums)
UnsupportedOperationException count not supported on this type: Symbol clojure.lang.RT.countFro (RT.java:556)
问题是colls
中的是文本表达式。这就是为什么宏domap
在传递整数序列时起作用,而在其他情况下不起作用。请注意'(nums)
的实例:
我尝试了
~
,~
,'
,let
和var
等多种组合,但都没有效果。尝试将其作为宏编写可能是一个错误,但我仍然很好奇如何编写一个包含这些复杂参数的可变宏。以下是宏无法工作的原因:
”(~@colls)
此表达式创建所有coll的引用列表。例如。如果您将其传递给(范围3)
,则此表达式将成为”((范围3))
,因此文字参数将是您的coll之一,阻止对(范围3)
的求值,这肯定不是您想要的
现在,如果您不在宏中引用(~@colls)
,那么它们当然会变成像((范围3))
这样的文本函数调用,这使得编译器在宏扩展时间之后抛出(它将尝试求值((0 1 2))
)
您可以使用list
来避免此问题:
(defmacro domap [f & colls]
`(dotimes [i# (apply min (map count (list ~@colls)))]
(apply ~f (map #(nth % i#) (list ~@colls)))))
=> (domap println (range 3))
0
1
2
但是有一件事很糟糕:在宏中,整个列表创建了两次。以下是我们可以避免的方法:
(defmacro domap [f & colls]
`(let [colls# (list ~@colls)]
(dotimes [i# (apply min (map count colls#))]
(apply ~f (map #(nth % i#) colls#)))))
Coll并不是我们唯一需要避免多次评估的东西。如果用户将类似于(fn[&args]…)
的内容作为f
传递,那么lambda也将在每个步骤中编译
现在,这正是您应该问自己为什么要编写宏的场景。从本质上讲,宏必须确保所有参数都经过求值,而不以任何方式进行转换。评估免费提供函数,因此让我们将其写成函数:
(defn domap [f & colls]
(dotimes [i (apply min (map count colls))]
(apply f (map #(nth % i) colls))))
考虑到您想要实现的功能,请注意已经有一个函数可以解决这个问题,dorun
,它只实现一个seq,但不保留头。例如:
`(dorun (map println (range 3)))
我也会这么做
现在您已经有了dorun
和map
,您可以使用comp
简单地组合它们来实现您的目标:
(def domap (comp dorun map))
=> (domap println (range 3) (range 10) (range 3))
0 0 0
1 1 1
2 2 2
谢谢,这正是我想要的。我没想到有必要显式调用list
——我认为backquote是(list…
)的语法糖。感谢关于函数多重评估的观点——我一直在想这个问题。虽然dorun
+map
是我想做的事情的自然方式,但我想指出的是,它并不总是最快的(请参阅我问题中的问题链接)。如果要防止对类似于此函数的函数进行多次求值,那么在let
绑定中添加f#f
是否可以实现这一点?是的,将f绑定到生成的符号一次可以解决此问题。然而,即使在下面的讨论中,您与A.Webb链接的答案也正确地指出,dorun
在空间复杂度上执行O(1),因为只有头部被保存在内存中。从宏生成循环可能运行得更快,但每一步的开销应该非常非常小。与NoiseSmith基准测试相比,请基准测试(comp-dorun-map)
我非常感谢我得到的帮助,我问题中的宏执行得非常糟糕,但是:反复被告知dorun
+map
是解决方案,这让人沮丧。视情况而定。这是做我想做的事情的最简单的方法,但是。。。。我一直在测试这一点,我知道这并不总是最快的<代码>(comp dorun map)
:54微秒在单个1000元素向量中迭代,使用每个元素作为向量clj向量的索引。噪声最大值mapv
定义:20-22微秒,doseq
19微秒,重现:35微秒,简单时间
19微秒。其他更糟。更正:doseq
需要12-13微秒。(对于没有读过其他问题的人(为什么要读?),是的,我使用的是Criterium。)这很可能是因为在输入序列上调用了seq
doseq
使用分块序列,这将在向量上工作得更快doseq
无疑是遍历一个序列以产生副作用的最惯用方法。在向量上,它的性能应该与向量上的dotimes
几乎相同(查找索引),这应该是最接近O(1)的。我提供了dorun+map
,因为您要求的是mapc
等价物mapv
创建了一系列结果,所以我永远不会建议只在副作用时使用它。