"Joy of Clojure" 2 版。清单 11.9 关于承诺

"Joy of Clojure" 2 edition. Listing 11.9 about promises

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

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

谢谢

tests好像是一个函数,所以:as把运行tests的结果(输出)放在all-tests上.

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

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

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

(如果我错了,hopefully someone will be inspired。)

为没有 The Joy of 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}))))))

最后调用 运行-tests 就像:

(run-tests pass fail fail fail pass)
=> #user.TestRun{:run 5, :passed 2, :failed 3}

最终,宏的后半部分正在做一个 let 赋值和 运行ning 主体,所以你最终得到

(let [tests tasks#
      results promises#]
  (into (TestRun. 0 0 0)
    ;; rest of body

在宏中,~n 不引用 '(let 周围的起始反引号,因此您可以将其读作 n,这是宏的第一个参数(好吧,第一个作为宏的第一个参数的向量的参数)。

这一切都发生在宏使用自定义 dothreads 设置承诺之后!使用线程池的函数 - 线程池中没有一个对理解宏很重要。

您可以通过将宏包装在 (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# 绑定似乎除了包装 tasks 之外没有提供任何额外的东西。起初我想知道这是否与 dothreads 中的不变性有关! call 或 macro-niceness 提供围绕使用的闭包,而不是直接使用宏的参数。

我尝试更改宏以完全删除 tasks# 并直接使用 ~tasks,这似乎仍然有效,因为 "tests" 不是正文中必需的绑定变量在 运行-tests 中,您可以同时删除宏中的 n 参数和最终 let 绑定的 ~n tasks# 部分而不会出现问题。

实际上,在读了好几遍之后,我终于明白了,就是让整个向量看起来像一个标准的解构绑定。

编辑:关于 "tests" 的更多解释。

这只是一个名字,它可能是"foo-tests","foo-bar",因为它最终被用来在let绑定中定义一些东西。

如果 运行-tests 主体是这样的:

(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(和结果)是如何被用来最终定义变量的(哎呀——你知道我的意思),这些变量可以在调用宏的主体部分中使用。主体是初始向量 [foo all-tests :as results] 之后的所有内容,但在原始代码中,已声明 tests 但未使用。