问题:如何&在宏中处理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)))))
Run Code Online (Sandbox Code Playgroud)
我已经意识到这不是一个好的写作方式domap- 我在这个问题上得到了很好的建议.但是,我仍然想知道我在这个过程中遇到的棘手的宏问题.
如果集合作为文字传递,则此方法有效:
user=> (domap println [0 1 2])
0
1
2
nil
Run Code Online (Sandbox Code Playgroud)
但是在这样的其他情况下不起作用:
user=> (domap println (range 3))
range
3
nil
Run Code Online (Sandbox Code Playgroud)
或者这一个:
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)
Run Code Online (Sandbox Code Playgroud)
问题是它是文字表达式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))))
Run Code Online (Sandbox Code Playgroud)
我已经试过的各种组合~,~@,',let与var#等什么也没有工作.尝试将其写为宏可能是一个错误,但我仍然很好奇如何编写一个带有这些复杂参数的可变参数宏.
这就是你的宏不起作用的原因:
'(~@colls)此表达式创建所有colls的引用列表.E. g.如果你传递它(range 3),这个表达式就变成了'((range 3)),所以文字参数将成为你的一个colls,阻止评估(range 3)肯定不是你想要的.
现在,如果你不引用(~@colls)宏,当然它们会成为一个文字函数调用((range 3)),这使得编译器在宏展开时间之后抛出(它会尝试eval ((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
Run Code Online (Sandbox Code Playgroud)
然而,有一件事是可怕的:在宏内部,整个列表创建两次.以下是我们如何避免这种情况:
(defmacro domap [f & colls]
`(let [colls# (list ~@colls)]
(dotimes [i# (apply min (map count colls#))]
(apply ~f (map #(nth % i#) colls#)))))
Run Code Online (Sandbox Code Playgroud)
colls不是我们需要防止多次评估的唯一因素.如果用户传递类似(fn [& args] ...)as的内容f,那么lambda也会在每一步中编译.
现在,这正是您应该问自己为什么要编写宏的情况.从本质上讲,您的宏必须确保所有参数都是eval'd,而不会以任何方式转换它们.评估免费提供功能,所以让我们把它写成一个函数:
(defn domap [f & colls]
(dotimes [i (apply min (map count colls))]
(apply f (map #(nth % i) colls))))
Run Code Online (Sandbox Code Playgroud)
鉴于你想要实现的目标,请注意已经有一个函数可以解决这个问题,dorun它只是实现了一个seq但是没有保留head.E. g.:
`(dorun (map println (range 3)))
Run Code Online (Sandbox Code Playgroud)
也会做的伎俩.
现在,你有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
Run Code Online (Sandbox Code Playgroud)