28

最近、clojure コードで次のイディオムを使用していることに気づきました。

(def *some-global-var* (ref {}))

(defn get-global-var []
  @*global-var*)

(defn update-global-var [val]
  (dosync (ref-set *global-var* val)))

ほとんどの場合、これは refs が提供するトランザクション セマンティクスを必要とする可能性のあるマルチスレッド コードでさえありません。参照はスレッド化されたコード以上のもののように感じますが、基本的には不変性を必要とするすべてのグローバルです。これにはより良い方法がありますか?binding または let だけを使用するようにコードをリファクタリングすることもできますが、一部のアプリケーションでは特に注意が必要です。

4

2 に答える 2

31

この種のパターンを見ると、私は常に ref ではなくアトムを使用します。トランザクションが必要なく、共有の変更可能なストレージの場所だけが必要な場合は、アトムが適しているようです。

たとえば、キーと値のペアの変更可能なマップの場合、次を使用します。

(def state (atom {}))

(defn get-state [key]
  (@state key))

(defn update-state [key val]
  (swap! state assoc key val))
于 2010-07-16T19:39:58.613 に答える
25

関数には副作用があります。同じ入力でそれらを 2 回呼び出すと、 の現在の値に応じて異なる戻り値が返される場合があり*some-global-var*ます。これにより、特にこれらのグローバル変数が複数ある場合は、テストと推論が難しくなります。

関数を呼び出す人は、ソースを検査しないと、関数がグローバル var の値に依存していることさえ知らない場合があります。グローバル変数を初期化するのを忘れた場合はどうなりますか? 忘れがちです。これらのグローバル変数に依存するライブラリを使用しようとする 2 つのコード セットがある場合はどうなるでしょうか? を使用しない限り、それらはおそらく互いに重なり合いますbinding。また、参照からデータにアクセスするたびにオーバーヘッドが追加されます。

副作用のないコードを書けば、これらの問題はなくなります。関数は独立しています。テストは簡単です。いくつかの入力を渡し、出力を検査すると、常に同じになります。関数がどの入力に依存しているかは簡単にわかります。それらはすべて引数リストにあります。これで、コードはスレッドセーフになりました。そして、おそらくより速く実行されます。

「一連のオブジェクト/メモリを変更する」スタイルのプログラミングに慣れている場合、このようにコードを考えるのは難しいですが、一度コツをつかめば、この方法でプログラムを編成するのは比較的簡単になります。コードは通常、同じコードのグローバル ミューテーション バージョンと同じか、それよりも単純になります。

これは非常に不自然な例です:

(def *address-book* (ref {}))

(defn add [name addr]
  (dosync (alter *address-book* assoc name addr)))

(defn report []
  (doseq [[name addr] @*address-book*]
    (println name ":" addr)))

(defn do-some-stuff []
  (add "Brian" "123 Bovine University Blvd.")
  (add "Roger" "456 Main St.")
  (report))

孤立して見るとdo-some-stuff、一体何をしているのですか?暗黙のうちに起こっていることがたくさんあります。このパスの下には、スパゲッティがあります。間違いなくより良いバージョン:

(defn make-address-book [] {})

(defn add [addr-book name addr]
  (assoc addr-book name addr))

(defn report [addr-book]
  (doseq [[name addr] addr-book]
    (println name ":" addr)))

(defn do-some-stuff []
  (let [addr-book (make-address-book)]
    (-> addr-book
        (add "Brian" "123 Bovine University Blvd.")
        (add "Roger" "456 Main St.")
        (report))))

do-some-stuff孤立していても、何が行われているかは明らかです。必要な数のアドレス帳を配置できます。複数のスレッドが独自のスレッドを持つことができます。このコードは、複数の名前空間から安全に使用できます。アドレス帳は引数として渡すので、忘れずに初期化してください。簡単にテストreportできます。必要な「モック」アドレス帳を渡して、何が印刷されるかを確認するだけです。現在テストしている関数以外は、グローバルな状態などを気にする必要はありません。

複数のスレッドからのデータ構造への更新を調整する必要がない場合は、通常、ref またはグローバル変数を使用する必要はありません。

于 2010-07-16T19:42:43.440 に答える