3

Clojure で定義された関数の結果に影響を与える可能性がある Vars をプログラムで特定するにはどうすればよいですか?

次の Clojure 関数の定義を考えてみましょう。

(def ^:dynamic *increment* 3)
(defn f [x]
  (+ x *increment*))

これは の関数ですがx*increment*(またclojure.core/+(1)の関数でもあります。しかし、私はそれについてあまり関心がありません)。この関数のテストを作成するときは、関連するすべての入力を確実に制御したいので、次のようにします。

(assert (= (binding [*increment* 3] (f 1)) 4))
(assert (= (binding [*increment* -1] (f 1)) 0))

(誰かが合理的に変更する可能性のある構成値であると想像してください*increment*。これが発生したときに、この関数のテストを変更する必要はありません。)

(f 1)私の質問は次のとおりです: の値が依存する可能性がある*increment*が他の Var には依存しないというアサーションをどのように記述すればよいですか? いつか誰かがコードをリファクタリングして関数を

(defn f [x]
  (+ x *increment* *additional-increment*))

テストを更新することを怠り、ゼロであってもテストを失敗させたいと思い*additional-increment*ます。

もちろん、これは単純化された例です。大規模なシステムでは、多数の動的 Var が存在する可能性があり、それらは関数呼び出しの長いチェーンを通じて参照される可能性があります。Var を参照するf呼び出しgが呼び出された場合でも、ソリューションは機能する必要があります。に依存してhいると主張しないのは素晴らしいことですが、これはそれほど重要ではありません。分析対象のコードがJava 相互運用機能を呼び出したり使用したりしている場合、もちろんすべての賭けは外れます。(with-out-str (prn "foo"))*out*eval

ソリューションの 3 つのカテゴリを考えることができます。

  1. コンパイラから情報を取得する

    存在しない Var を参照しようとすると、次のようにスローされるため、コンパイラは関数定義をスキャンして必要な情報を取得すると思います。

    user=> (defn g [x] (if true x (+ *foobar* x)))
    CompilerException java.lang.RuntimeException: Unable to resolve symbol: *foobar* in this context, compiling:(NO_SOURCE_PATH:24) 
    

    これは、問題のあるコードが実行されるかどうかに関係なく、コンパイル時に発生することに注意してください。したがって、コンパイラは、関数によって参照される可能性のある Vars を認識している必要があり、その情報にアクセスしたいと考えています。

  2. ソース コードを解析して構文ツリーをたどり、Var がいつ参照されたかを記録する

    コードはデータであり、そのすべてだからです。macroexpandこれは、各 Clojure プリミティブとそれらが使用するあらゆる種類の構文を呼び出して処理することを意味すると思います。これはコンパイル フェーズに非常によく似ているため、コンパイラの一部を呼び出したり、独自のフックをコンパイラに追加したりできれば素晴らしいと思います。

  3. Var メカニズムを計測し、テストを実行して、どの Var がアクセスされるかを確認します。

    他の方法ほど完全ではありませんが (私のテストが失敗したコードのブランチで Var が使用されている場合はどうなるでしょうか?)、これで十分です。defVar のように動作するが、何らかの方法でそのアクセスを記録するものを作成するには、再定義する必要があると思います。


(1)実際には、再バインドしてもその特定の機能は変わりません+。しかし、Clojure 1.2 では、最適化を行うことでその最適化をバイパスでき、それ(defn f [x] (+ x 0 *increment*))から を楽しむことができます(binding [+ -] (f 3))。Clojure 1.3 では、再バインドを試みると+エラーがスローされます。

4

2 に答える 2

5

analyze最初の点については、ライブラリの使用を検討できます。これを使用すると、式でどの動的変数が使用されているかを非常に簡単に把握できます。

user> (def ^:dynamic *increment* 3)
user> (def src '(defn f [x]
                  (+ x *increment*)))
user> (def env {:ns {:name 'user} :context :eval})
user> (->> (analyze-one env src) 
           expr-seq 
           (filter (op= :var)) 
           (map :var) 
           (filter (comp :dynamic meta)) 
           set)
#{#'user/*increment*}
于 2012-02-27T20:37:09.823 に答える
0

これはあなたの質問に答えないことを私は知っていますが、一方のバージョンに自由変数がなく、もう一方のバージョンが最初のバージョンを適切なトップで呼び出す関数の2つのバージョンを提供するだけではるかに少ない作業になるでしょう-レベルは定義しますか?

例えば:

(def ^:dynamic *increment* 3)
(defn f
  ([x]
     (f x *increment*))
  ([x y]
     (+ x y)))

(f x y)このようにして、グローバル状態に依存しない、に対するすべてのテストを記述できます。

于 2012-02-27T20:47:12.100 に答える