5

clojure-twitter などの一部のライブラリは、oauth 認証に特別な変数 (アスタリスクで囲まれた動的バインディング用のもの) を使用していることに気付きました。認証を var に保存してから (with-oauth myauth ..) を使用します。アプリケーションの各ユーザーの認証変数を再バインドできるため、これはこの種の問題に対する非常に優れたソリューションだと思います。

私が書いている電子メールクライアントで同様のルートをたどりました。現在のユーザーのセッションとユーザー情報を含むマップにバインドするsessionという名前の特別な var があり、その var からの情報を使用するさまざまな重要な機能があります。with-session に渡された一連のフォームのコンテキストで一時的に再バインドするマクロ with-session を作成しました。それは(私にとって)かなりきれいな解決策であることがわかりました。

それで、私の質問はこれです:私はそれを儀式で「やっている」のですか?これは悪い設計上の決定ですか、それともこれは特別な変数の意図された使用法の 1 つですか?

4

3 に答える 3

7

あなたはそれを正確にやっているようです。実際、同様に機能するビルトイン / contrib マクロがいくつwith-out-strかありclojure.contrib.sql/with-connectionます。後者は、現在の Clojure インフラストラクチャのかなり重要な部分であるため、それが使用するイディオムが何であれ、多くの人々によって精査されてきました。

心に留めておくべき重要な問題は、bindings/with-bindingsフォームのスコープ内にあるときに起動するスレッドは、問題の変数のリバウンド値を継承しないということです。むしろ、ルート バインディングが表示されます。バインディングをワーカー スレッド/エージェントに伝達する場合は、それらを明示的に (たとえば、関数の引数として) 渡すか、bound-fn.

于 2010-02-15T04:10:41.753 に答える
3

再バインドする予定のグローバル変数を作成するたびに、その変数にアクセスするすべての関数に暗黙の引数を追加します。適切な(明示的な)引数とは異なり、この非表示の引数は関数のシグネチャに表示されず、関数がそれを使用していることを示すものはほとんどない場合があります。コードの「機能」が低下します。同じ引数を使用して同じ関数を呼び出すと、これらのグローバル動的変数の現在の状態に基づいて異なる戻り値が返される可能性があります。

グローバル変数の利点は、デフォルト値を簡単に指定できることです。また、その変数を使用するすべての関数にその変数を渡す必要がないため、怠惰になる可能性があります。

欠点は、コードの読み取り、テスト、使用、およびデバッグが難しいことです。また、コードは潜在的にエラーが発生しやすくなります。sessionvarを使用する関数を呼び出す前に、varをバインドまたは再バインドすることを忘れるのは簡単ですが、パラメーターがarglistにあるときにパラメーターを渡すのを忘れるのはそれほど簡単ではありません。

そのため、謎のバグや、関数間の奇妙な暗黙の依存関係が発生します。このシナリオを考えてみましょう。

user> (defn foo [] (when-not (:logged-in *session*) (throw (Exception. "Access denied!"))))
#'user/foo
user> (defn bar [] (foo))
#'user/bar
user> (defn quux [] (bar))
#'user/quux
user> (quux)
; Evaluation aborted.  ;; Access denied!

の動作は、値を持つセッションに暗黙的に依存しますが、すべての関数呼び出し、およびそれらの関数が呼び出すすべてのquux関数を掘り下げない限り、それはわかりません。quux深さ10または20レベルのコールチェーンを想像してみてください。下部にある関数は、に依存し*session*ます。それをデバッグして楽しんでください。

代わりに、、、を持っていた場合は、(defn foo [session] ...)電話をかけると、セッションの準備ができていることがすぐにわかります。(defn bar [session] ...)(defn quux [session] ...)quux

個人的には、大量の関数が使用する強力で正常なデフォルト値がない限り、明示的な引数を使用します。これは、非常にまれに、または再バインドしないことを計画していました。(たとえば、何かを出力したいすべての関数に明示的な引数としてSTDOUTを渡すのはばかげています。)

于 2010-02-15T20:07:13.933 に答える
1

バインド関数は、テスト コードに最適です。

テストコードでラッパー関数の再バインドを広範囲に使用して、乱数ジェネレーターのモックアップ、固定ブロックサイズの使用などを行い、既知の出力に対して暗号化関数を実際にテストできるようにします。

(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]))
             com.cryptovide.modmath/mody 719
             com.cryptovide.modmath/field-size 10]
        ~@exprs))

(is (= (with-fake-prng (encrypt-string "asdf")) [23 54 13 63]))  

When using bindings its useful to remember that they only rebind for the current thread so when you fire something off in pmap which uses the thread pool you may loose your bindings. If you have some code that builds a string in parallel like this:

(with-out-str
    (pmap process-data input))

Using that innocent looping \p in front of the map will cause the binding to go away because it will run the process-data function in several threads from the thread-pool.

EDIT: Michał Marczyk points out the bound-fnスレッドを使用するときにバインドが失われないようにするために使用できるマクロ。

于 2010-02-16T18:09:46.723 に答える