问题:如果在宏中&
之后处理一个catch-all参数,当要传递的参数是序列时,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
的好方法 - 我在this问题中得到了很好的建议。但是,我仍然想知道我在这个过程中遇到的棘手的宏问题。
如果集合以文字形式传递,则此方法有效:
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)
问题在于它是colls
内的文字表达式。这就是宏domap
在传递整数序列时的工作原理,但在其他情况下则不然。请注意'(nums)
:
user=> (pprint (macroexpand-1 '(domap println nums)))
(clojure.core/dotimes
[i__199__auto__
(clojure.core/apply
clojure.core/min
(clojure.core/map clojure.core/count '(nums)))]
(clojure.core/apply
println
(clojure.core/map
(fn*
[p1__198__200__auto__]
(clojure.core/nth p1__198__200__auto__ i__199__auto__))
'(nums))))
我尝试了~
,~@
,'
,let
和var#
的各种组合,等等。没有任何效果。尝试将其写为宏可能是一个错误,但我仍然很好奇如何编写一个采用类似复杂参数的可变参数宏。
答案 0 :(得分:5)
以下是您的宏不起作用的原因:
'(~@colls)
此表达式创建所有colls的引用列表。 E. g。如果你传递(range 3)
,这个表达式会变成'((range 3))
,所以 literal 参数将成为你的一个colls,阻止评估(range 3)
当然不是你想要的这里。
现在,如果你不在宏中引用(~@colls)
,当然它们会成为像((range 3))
那样的文字函数调用,这使得编译器在宏展开时间之后抛出(它会尝试eval {{ 1}})。
您可以使用((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
colls并不是我们需要多次阻止评估的唯一因素。如果用户传递(defmacro domap [f & colls]
`(let [colls# (list ~@colls)]
(dotimes [i# (apply min (map count colls#))]
(apply ~f (map #(nth % i#) colls#)))))
之类的(fn [& args] ...)
,那么lambda也会在每一步中编译。
现在这就是你应该问自己为什么要编写宏的情况。从本质上讲,您的宏必须确保所有参数都是eval'd,而不会以任何方式转换它们。评估免费提供功能,所以让我们把它写成一个函数:
f
考虑到你想要实现的目标,请注意有一个函数可以解决,(defn domap [f & colls]
(dotimes [i (apply min (map count colls))]
(apply f (map #(nth % i) colls))))
只是简单地实现seq但不保留dorun
。 E. g。:
head
也可以做到这一点。
现在您已拥有`(dorun (map println (range 3)))
和dorun
,您只需使用map
撰写这些内容即可实现目标:
comp