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 ~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# 等。没有任何效果。尝试将其写成宏可能是错误的,但我仍然很好奇如何编写一个可变参数宏来接受像这样的复杂参数。

最佳答案

这就是您的宏不起作用的原因:

'(~@colls) 此表达式创建所有 col 的引用列表。例如如果你传递它 (range 3),这个表达式变成 '((range 3)),所以 literal 参数将是你的参数之一colls,防止评估 (range 3) 当然不是你想要的。

现在,如果您不在宏中引用 (~@colls),它们当然会变成像 ((range 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#)))))

cols 并不是我们唯一需要防止被多次求值的东西。如果用户将 (fn [& args] ...) 作为 f 传递,则该 lambda 也会在每一步中编译。

现在这正是您应该问自己为什么要编写宏的场景。本质上,您的宏必须确保所有参数都经过评估,而无需以任何方式转换它们。评估是免费的函数,所以让我们把它写成一个函数:

(defn domap [f & colls]
  (dotimes [i (apply min (map count colls))]
    (apply f (map #(nth % i) colls)))) 

鉴于您想要实现的目标,请注意已经有一个函数可以解决该问题,dorun 只是实现了一个 seq 但不保留 head。例如:

`(dorun (map println (range 3)))

也可以做到这一点。

现在您已经有了 dorunmap,您可以简单地使用 comp 组合它们来实现您的目标:

(def domap (comp dorun map))

=> (domap println (range 3) (range 10) (range 3))

0 0 0
1 1 1
2 2 2

关于macros - Clojure 可变参数宏迭代收集在 & 额外参数中的序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21542365/

相关文章:

c++ - C 预处理器扩展到另一个类似对象的宏

java - 导入 freemarker 宏

c++ - 如何在 SQL 中创建类似 DECODE 函数的 C++ 宏?

xml - 在 Clojure 中如何在集合上应用函数

ruby-on-rails - 如何在 ruby​​/rails 中定义 C/C++ 样式的宏

macros - 用英文字符替换土耳其字符

clojure - 有没有办法查看http-kit生成的请求?

macros - 命名空间混淆和宏

clojure - 关于 Clojure 中堆和垃圾的初学者问题

unit-testing - Clojure.spec:基于生成器中其他字段的字段存在