答案 0 :(得分:4)
为没有“Clojure的喜悦”的人设置问题的背景(我很喜欢这本书),所讨论的宏是:
(defmacro with-promises [[n tasks _ as] & body]
(when as
`(let [tasks# ~tasks
n# (count tasks#)
promises# (take n# (repeatedly promise))]
(dotimes [i# n#]
(dothreads!
(fn []
(deliver (nth promises# i#)
((nth tasks# i#))))))
(let [~n tasks#
~as promises#]
~@body))))
并且因此被使用:
(defn run-tests [& all-tests]
(with-promises
[tests all-tests :as results]
(into (TestRun. 0 0 0)
(reduce #(merge-with + %1 %2) {}
(for [r results]
(if @r
{:run 1 :passed 1}
{:run 1 :failed 1}))))))
最后调用run-tests就像:
(run-tests pass fail fail fail pass)
=> #user.TestRun{:run 5, :passed 2, :failed 3}
最终,宏的后半部分正在执行一个let赋值并运行正文,所以你最终得到了
(let [tests tasks#
results promises#]
(into (TestRun. 0 0 0)
;; rest of body
在宏中,~n在'(let
周围没有引用开始的反向标记,所以你可以把它读作n
这是宏的第一个参数(好吧,第一个参数是作为宏的第一个参数的向量。)
这一切都发生在宏使用自定义dothreads设置promises之后!使用线程池的函数 - 其中非线程池对于理解宏非常重要。
您可以通过将宏包装在(pprint (macroexpand-1 '(with-promises ...
中来生成类似的东西来确定更多关于宏的信息(我用更简单的东西替换了自动生成的名称,v1,n1,p1和i1):
(clojure.core/let
[v1 all-tests
n1 (clojure.core/count v1)
p1 (clojure.core/take n1 (clojure.core/repeatedly clojure.core/promise))]
(clojure.core/dotimes
[i1 n1]
(user/dothreads!
(clojure.core/fn
[]
(clojure.core/deliver
(clojure.core/nth p1 i1)
((clojure.core/nth v1 i1))))))
(clojure.core/let
[tests v1
results p1]
(into
(TestRun. 0 0 0)
;; ... rest of main body
清楚地显示传入的参数在最终的let绑定中用作变量。
但是,在此示例用法(即run-tests
函数)中,tests
变量实际上并未在with-promises调用的主体中使用,只有results
,所以你提出质疑是对的,根本不需要。
查看宏定义,对于这种情况可能会有进一步的优化,因为任务#binding似乎没有提供任何额外的包装tasks
。起初我想知道这是否与dothreads中的不变性有关! call或macro-niceness用于提供围绕用法的闭包,而不是直接使用宏参数。
我尝试更改宏以完全删除tasks#
并直接使用~tasks
,这似乎仍然有效,并且“test”不是运行测试体中所需的绑定变量,您可以同时删除宏中的n
参数和最终let绑定的~n tasks#
部分。
实际上,经过几次阅读之后,它终于明白了,这就是让整个载体读起来像标准的解构结合。
编辑:关于“测试”的更多解释。
这只是一个名字,它可能是“foo-tests”,“foo-bar”,因为它最终用于定义let绑定中的某些内容。
如果运行测试机构类似于:
(defn run-tests [& all-tests]
(with-promises
[foo all-tests :as results]
(println "foo was set to" foo)
(into (TestRun. 0 0 0)
;; rest of body
你可以看到foo(和结果)如何用于最终定义变量(eck - 你知道我的意思),可以在宏调用的正文部分中使用。主体是初始向量[foo all-tests :as results]
之后的所有内容,但在原始代码中,tests
已声明但未使用。
答案 1 :(得分:1)
tests
似乎是一个函数,因此:as
会将tests
的结果(输出)置于all-tests
。
(编辑:)
仔细检查后,with-promises
宏似乎将tests
设置为测试计数。
从我正在阅读的内容(对宏不太了解),参数似乎映射("tests" "all-tests" ":as" "results") -> ("n" "tasks" "_" "as")
,但我不能得到的是暗示when
需要一个值results
(“as”)当我们应该创建它时。无论如何,tests
的值是在宏的最终let
中设置的。
根据我的拙见,这段代码远太聪明了。 Fogus是一位大师,但这不是他最好的作品。
(如果我错了,hopefully someone will be inspired。)