我正在检查指定书的清单 11.9(pdf 的第 269 页)。
谁能解释一下tests
价值是如何设定的(行[tests all-tests :as results]
)?
谢谢
为了给没有《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 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设置promise之后!使用线程池的函数 - 对于理解宏很重要。
您可以通过将宏包装在生成类似内容的 a 中来确定有关宏的更多信息(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
,所以你质疑它是正确的,它根本不需要。
查看宏定义,可能会针对这种情况进行进一步优化,因为 tasks# 绑定似乎除了 wrapping 之外没有提供任何其他功能tasks
。起初我想知道这是否与 dothreads 中的不变性有关!调用或宏的优点,用于提供围绕用法的闭包,而不是直接使用宏的参数。
我尝试更改宏以tasks#
完全直接删除 use ~tasks
,这似乎仍然有效,并且由于“测试”不是运行测试正文中必需的绑定变量,您可以n
从宏中删除参数,并且~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
已声明但未使用。
tests
似乎是一个函数,因此:as
将运行的结果(输出)tests
放在all-tests
.
(编辑:)经过仔细检查,with-promises
宏似乎将 设置tests
为测试计数。
从我正在阅读的内容(对宏了解不多)来看,参数似乎映射("tests" "all-tests" ":as" "results") -> ("n" "tasks" "_" "as")
但我不太明白的是,当我们应该创建时,这意味着 ("as")when
需要一个值results
它。无论如何, 的值是在宏tests
的最后设置的。let
在我看来,这段代码太聪明了。福格斯是一位大师,但这不是他最好的作品。
(如果我错了,希望有人会受到启发。)