Clojure可变参数宏迭代在&收集的序列中。额外参数

时间:2014-02-04 03:22:53

标签: 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 ~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))))

我尝试了~~@'letvar#的各种组合,等等。没有任何效果。尝试将其写为宏可能是一个错误,但我仍然很好奇如何编写一个采用类似复杂参数的可变参数宏。

1 个答案:

答案 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