2

名前の付いた本のListing 11.9 (pdf の p.269) を調べています。

値がどのように設定されているかを誰かに説明してもらえますかtests(行[tests all-tests :as results])?

ありがとう

4

2 に答える 2

4

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}))))))

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 を使用して promise をセットアップした後に発生します! スレッドプールを使用する関数 - マクロを理解するために重要なものはありません。

次のようなものを生成する でラップすることにより、マクロについて詳しく判断できます(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。最初は、これは dothread の不変性に関するものなのだろうかと思いました! パラメータをマクロに直接使用するのではなく、使用方法の周りにクロージャーを提供するための呼び出し、またはマクロナイスネス。

tasks#マクロを変更してuse を完全に直接削除しようとしましたが~tasks、これはまだ機能しているようです。「tests」は実行テストの本体で必須のバインド変数ではないためn、マクロからパラメーターと~n tasks#問題なく最終的な let バインディングの一部。

実際、何度か読んだ後、最終的には、ベクトル全体を標準の破壊バインディングのように読み取らせることであることに気付きました。

編集:「テスト」に関するもう少し説明。

これは単なる名前です。「foo-tests」、「foo-bar」などの可能性があります。これは、最終的に let バインディングで何かを定義するために使用されるためです。

run-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は宣言されていますが、使用されていません。

于 2015-05-11T16:27:40.683 に答える
1

testsは関数のように見えるので:as、実行の結果 (出力) をtestson に置きall-testsます。

(編集:) 注意深く調べると、with-promisesマクロはtestsをテストの数に設定しているように見えます。

私が読んでいるもの(マクロについてはあまり知りません)から、引数はマップされているように見えますが、作成することになっているときに(「as」)の値が必要である("tests" "all-tests" ":as" "results") -> ("n" "tasks" "_" "as")ことを意味することはわかりませんそれ。いずれにせよ、の値はマクロの final に設定されます。whenresultstestslet

私の謙虚な意見では、このコードはあまりにも巧妙です。Fogus はマスターですが、これは彼の最高の作品ではありません。

(私が間違っていれば、うまくいけば誰かがインスピレーションを受けるでしょう。)

于 2015-05-11T12:51:06.510 に答える