2

我正在检查指定书的清单 11.9(pdf 的第 269 页)。

谁能解释一下tests价值是如何设定的(行[tests all-tests :as results])?

谢谢

4

2 回答 2

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 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已声明但未使用。

于 2015-05-11T16:27:40.683 回答
1

tests似乎是一个函数,因此:as将运行的结果(输出)tests放在all-tests.

(编辑:)经过仔细检查,with-promises宏似乎将 设置tests为测试计数。

从我正在阅读的内容(对宏了解不多)来看,参数似乎映射("tests" "all-tests" ":as" "results") -> ("n" "tasks" "_" "as")但我不太明白的是,当我们应该创建时,这意味着 ("as")when需要一个值results它。无论如何, 的值是在宏tests的最后设置的。let

在我看来,这段代码太聪明了。福格斯是一位大师,但这不是他最好的作品。

(如果我错了,希望有人会受到启发。)

于 2015-05-11T12:51:06.510 回答