“Clojure的喜悦”2版。关于promises的清单11.9

时间:2015-05-11 12:43:53

标签: clojure macros promise mutation

我正在研究这本书的Listing 11.9(pdf第269页)。

有人能解释我如何设置tests值(行[tests all-tests :as results])?

感谢

2 个答案:

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