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 つのカテゴリを考えることができます。
コンパイラから情報を取得する
存在しない 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 を認識している必要があり、その情報にアクセスしたいと考えています。
ソース コードを解析して構文ツリーをたどり、Var がいつ参照されたかを記録する
コードはデータであり、そのすべてだからです。
macroexpand
これは、各 Clojure プリミティブとそれらが使用するあらゆる種類の構文を呼び出して処理することを意味すると思います。これはコンパイル フェーズに非常によく似ているため、コンパイラの一部を呼び出したり、独自のフックをコンパイラに追加したりできれば素晴らしいと思います。Var メカニズムを計測し、テストを実行して、どの Var がアクセスされるかを確認します。
他の方法ほど完全ではありませんが (私のテストが失敗したコードのブランチで Var が使用されている場合はどうなるでしょうか?)、これで十分です。
def
Var のように動作するが、何らかの方法でそのアクセスを記録するものを作成するには、再定義する必要があると思います。
(1)実際には、再バインドしてもその特定の機能は変わりません+
。しかし、Clojure 1.2 では、最適化を行うことでその最適化をバイパスでき、それ(defn f [x] (+ x 0 *increment*))
から を楽しむことができます(binding [+ -] (f 3))
。Clojure 1.3 では、再バインドを試みると+
エラーがスローされます。