(go (>! c 42))
返信を待たずにメッセージを送信するために、次のようなことをするのは非常に魅力的です。
(go (>! c 42))
goブロックは安価ですが、完全に無料ではありません。そのため、以下を使用することをお勧めします。
(async/put! c 42)
go
は最終的に put!
を呼び出すだけなので、実際にはデメリットはありません。
また、コードがコールバック内で呼び出されており、バックプレッシャーを尊重したい場合は、再帰関数を put!
と共に使用してバックプレッシャーを尊重するのが非常に簡単です。
(defn http-call
"Makes an async call to a web browser"
[url callback] ...)
(def urls [url1 url2 url3])
(defn load-urls
"Spools the results of loading several urls onto a channel.
does this without creating temporary channels or go blocks"
[urls out-c]
(http-call
(first urls)
(fn [response]
(put! out-c response (fn [_] (load-urls (next urls) out-c))))))
(load-urls urls response-chan)
この例では、アプリケーションでチャネルの操作を開始できる優れたクリーンなインターロップコードがあります。大量のチャネルやgoを作成して、作成後すぐに破棄する必要はありません。
バックプレッシャーを尊重することが重要であることに注意してください。core.asyncの一般的な原則は、アンバウンドキューは悪であり、保留中のputの数は制限されている(現在は1024)ということです。もう1つのオプションは、dropping-buffer
や sliding-buffer
のように、常にputをすぐに受け入れるバッファ付きのチャネルを使用することです。
goマクロは、関数作成の境界で翻訳を停止します。つまり、次のコードはコンパイルに失敗するか、<!
がgoブロックの外で使用されたというランタイムエラーをスローする可能性があります。
(go (let [my-fn (fn [] (<! c))] (my-fn)))
多くのClojure構造がマクロ内で関数を作成するため、これは覚えておくべきことの1つです。以下は、期待どおりに動作しないコードの例です。
(go (map <! some-chan))
(go (for [x xs]
(<! x)))
ただし、doseq
など、他のClojure構造は内部的にクロージャを割り当てません。
; This works just fine
(go (doseq [c cs]
(println (<! c)))
残念ながら、現時点では、ソースを確認するか、マクロによって生成されたコードをテストしない限り、特定のマクロがgoブロック内で期待どおりに動作するかどうかを知る良い方法はありません。
「なぜgoブロックの変換は関数の作成で停止するのか?」に対する最良の説明は、基本的に型に関する問題に帰着します。次のスニペットを見てみましょう。
(map str [1 2 3])
str
の出力型が文字列であるため、これが文字列の seq
を生成することは簡単にわかります。では、async/<!
の戻り値の型は何でしょうか? goブロックのコンテキストでは、それはチャネルから取得されたオブジェクトです。しかし、goブロックはそれを async/put!
へのパーキング呼び出しに変換する必要があります。 async/<!
の戻り値の型は、実際には Async<Object>
や Promise<Object>
に近いものと考える必要があります。したがって、(map async/<! chans)
の結果は「保留中のチャネル操作のseq」のようなもので、まったく意味がありません。
要するに、goマクロは、いくつかの重大な作業なしにはこれらの操作を実行できません。 Erjang のような他の言語では、JVM全体でコードを変換することにより、このような構造を許可しています。これは、core.async では避けたいことです。複雑になり、1つのライブラリのロジックがJVM全体のコードに影響を与えるからです。そのため、実用的な妥協点として、(fn [] …)
を見たときに変換を停止します。
オリジナルの著者:Timothy Baldridge