2011-08-24 7 views
11

bindingフォームでは、クロージャーで再バインド可能な動的スコープが可能であることを理解しています。これまで使用してきた唯一の用途は、printなどのI/Oの場合です。*out*は、当時のライターにリバウンドしています。clojureにバインディングを使用する良い例は何ですか?

本当に他の施設が実際には機能しないbindingの力を活用した例を見たいと思います。私が個人的に使用したのは、ユーザーが提供したオブジェクトをすべての関数に渡すことが面倒だった場合のみです。基本的には、ヘルパー関数が使用するコンテキストを作成しようとしています。 (このケースに似てWhen should one use the temporarily-rebind-a-special-var idiom in Clojure?)具体的には、データベース機能が何を操作するのかを知るために、ユーザに*db* varへの動的バインディングを作成することを依頼していました。これは、ユーザーが多数のネストされた呼び出しをデータベース関数に書き込む必要がある場合に特に便利でした。一般的には、自分で簡単にマクロを作成する必要がある場合は、大丈夫ですが、そうするようにユーザーに要求することは悪いようです。それは言われているように、私は可能な限りそうすることを避けようとしています。

私がコピーして自分のコードに組み込むことができる「バインディング」の他の良い使用例は何ですか?

+0

これまでのすべての良い答え。他の誰かが刺すことを望む場合には、答えを選択する前に、多分数日前にそれを残しておきます。 – bmillare

答えて

8

は、私は2つの理由でバインディングを使用します。そのようなデータベース接続またはメッセージブローカチャネルとして「グローバル」リソースを使用して

    1. は定数または他の記号の他の値を上書きテストを実行

    テスト

    私はいくつかのコンポーネントメッセージ交換を介してメッセージを送信することによって通信する。

    (ns const) 
    (def JOB-EXCHANGE "my.job.xchg") 
    (def CRUNCH-EXCHANGE "my.crunch.xchg") 
    ;; ... more constants 
    

    これらの定数は、適切な場所にメッセージを送信するために多くの場所で使用されています。これらの交換は、私は、このようなように定義したグローバル名を、持っています。私のコードをテストするために、私のテストスイートの一部は、実際のメッセージ交換を使用するコードを実行します。しかし、私のテストが実際のシステムに干渉することは望ましくありません。

    これを解決するために、私はこれらの定数より優先されますbindingコールに私のテストコードをラップ:このbinding関数の内部

    ;; in my testing code: 
    (binding [const/CRUNCH-EXCHANGE (str const/CRUNCH-EXCHANGE (gensym "-TEST-")) 
          const/CRUNCH-TASK-QUEUE (str const/CRUNCH-TASK-QUEUE (gensym "-TEST-"))] 
        ;; tests here 
    ) 
    

    を、私は定数を使用して任意のコードを呼び出すことができますし、それが使用しますオーバーライドされた値。グローバルリソース

    私はバインディングを使用する別の方法を使用して

    特定のスコープ内でグローバルまたはシングルトンリソースの値を「修正」することです。ここに私のコードは、それを使用することができるように、私はシンボル*amqp-connection*にRabbitMQのConnectionの値をバインドし、私が書いたRabbitMQのライブラリの例です:

    (with-connection (make-connection opts) 
        ;; code that uses a RabbitMQ connection 
    ) 
    

    with-connectionの実装は非常に簡単です:

    (def ^{:dynamic true} *amqp-connection* nil) 
    
    (defmacro with-connection 
        "Binds connection to a value you can retrieve 
        with (current-connection) within body." 
        [conn & body] 
        `(binding [*amqp-connection* ~conn] 
        [email protected])) 
    

    私のRabbitMQライブラリ内のコードは、*amqp-connection*で接続を使用でき、それが有効であると仮定してConnectionとします。あなたは同じJVMで実行されているいくつかのreplsを持っているかもしれません

    (defn current-connection 
        "If used within (with-connection conn ...), 
        returns the currently bound connection." 
        [] 
        (if (current-connection?) 
        *amqp-connection* 
        (throw (RuntimeException. 
         "No current connection. Use (with-connection conn ...) to bind a connection.")))) 
    
  • +0

    '(if(current-connection?)'式は最後のコードブロックで正しいですか? 'if(* amqp-connection *)'か? "true?* amqp-connection *)'または '(some?* amqp-connection *)'? –

    +1

    Kenny、 'current-connection? 'は、var' * amqp-connection * 'をビューから'隠す 'その実装はあなたが言ったように '(true?* amqp-connection *)と同じくらい単純なものにすることができます。それは私が想定しているコードスタイルの問題です。 – Gert

    2

    VimClojureバックエンドで:それとも、with-connectionであなたのRabbitMQの呼び出しをラップするのを忘れて、記述例外をスロー(current-connection)機能を使用します。しかし、Vimとバックエンドの間の接続は連続していないので、潜在的に各コマンドに対して新しいスレッドを取得します。したがって、コマンド間で簡単に状態を保持することはできません。

    VimClojureの機能は次のとおりです。それは*warn-on-reflection*,*1,*2のようなすべての興味深いバールでbindingを設定します。その後、コマンドを実行し、その後、変更されたVarsをbindingからいくつかのBookeインフラストラクチャに格納します。

    すべてのコマンドでは、「私は4711にレプリケートします」と表示され、そのレプリケートの状態が表示されます。 replの状態に影響を与えずに0815.

    2

    バインディング機能は、テストコードで本当に役立ちます。これは、関数をvarsに格納する大きな利点の1つです(Clojureのデフォルト設定)。

    私が書いた暗号プログラムの抜粋。

    (defmacro with-fake-prng [ & exprs ] 
        "replaces the prng with one that produces consisten results" 
        `(binding [com.cryptovide.split/get-prng (fn [] (cycle [1 2 3]))] 
        [email protected])) 
    

    キージェネレータの機能をどのようにユニットテストしますか?それは予測できないと思われる。あなたはどこでも(if testing ...)をスレッドするか、何らかの種類の模擬フレームワークを使用することができます。または乱数ジェネレータを「動的に疑似」するマクロを使用して、このをテストコードにのみ入れて、生産側にcruftを残すことができます。

    (deftest test-key-gen 
        (with-fake-prng 
         ....)) 
    
    関連する問題