Where is def?
昨日の記事の中で
def のソースコードはどこにあるか分からなかったので、実際に def がどういう流れで登録してるのかは分かんなかったんだけど
って書いたけど、Clojure は JAR ファイルで配布されてるんだから、その中のどっかにあるはずだよなぁと思って、確認したら見つけた。ヽ(=´▽`=)ノ
clojure/Compiler.java at master · clojure/clojure · GitHub
というか、昨日の時点で REPL のコールスタックを確認すればすぐに気付けたことだった。寝て起きるの大切。
How does def work?
じゃ、def の流れを追いかけてみよう。Cursive の REPL でデバッグ実行しながら def してみる。例によって雰囲気です!
(def abcde "foobar")
じっこうー!
eval
まず最初にここに入ってくる↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7135
public static Object eval(Object form) { return eval(form, true); } public static Object eval(Object form, boolean freshLoader) {
上の方が呼ばれて、すぐ下に入ってきて、引数はこんな感じ↓
昨日ちょっと見た Var を使って、スレッドローカルに行番号などの情報を保持しつつ、今回は関係ないけど macroexpand が呼び出されて、今回の def の場合は、ここに入る↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7185-L7186
Expr expr = analyze(C.EVAL, form);
return expr.eval();
まず Expr オブジェクトを取得してから、そのオブジェクトの eval() を呼び出してる
analyze
analyze で Expr オブジェクトを取得してる。まずはここに入ってくる↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6748
public static Expr analyze(C context, Object form) { return analyze(context, form, null); } private static Expr analyze(C context, Object form, String name) {
context には "EVAL" が入ってる。これも上の方に入ってきてすぐ下のメソッドが呼び出されるので name は null
form に合わせて分岐に入るんだけど、さっき画像で見た通り3つの要素を持った PersistentList なので、入るのはこの分岐↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6793
else if(form instanceof ISeq) return analyzeSeq(context, (ISeq) form, name);
analyzeSeq
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7085
form の最初の要素をチェックしてここに来る↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7110-L7111
else if((p = (IParser) specials.valAt(op)) != null) return p.parse(context, form);
この specials はここで定義されていて↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L105
内容はこうなってるので↓
static final public IPersistentMap specials = PersistentHashMap.create( DEF, new DefExpr.Parser(),
DefExpr の Parser の parse() が呼び出される。近づいてきたー!
parse
ということで parse が呼び出されて↓
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L530
static class Parser implements IParser{ public Expr parse(C context, Object form) {
↓ここで form の2番目の要素から Symbol を取得して、その Symbol を使って Var を lookup してる
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L544
Symbol sym = (Symbol) RT.second(form);
Var v = lookupVar(sym, true);
↓lookupVar の中ではここにたどり着いて、現在のネームスペースのマッピングに Symbol がなければ intern して新しい Var をマッピングに登録。あればそれをそのまま返している
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7485
Object o = currentNS().getMapping(sym); if(o == null) { //introduce a new var in the current ns if(internNew) var = currentNS().intern(Symbol.intern(sym.name)); } else if(o instanceof Var) { var = (Var) o; }
で、最終的には DefExpr のコンストラクターが呼ばれて、生成されたインスタンスが返される
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L424
コンストラクターの引数はこう↓
Var は初回だと Unbound。二回目以降だと元々マッピングされてた Var なので前回の値が root にバインドされてる状態
今回の場合は def に値を渡してるので、それが init として渡されてて、initProvided が true になってる
これで DefExpr のインスタンスが取得できた!ので、次にそのインスタンスに対して eval ()を呼び出す
eval()
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L449
eval はとてもシンプルで、初期値を指定している場合には bindRoot を呼び出してる
var.bindRoot(init.eval());
おわりー!
すっきり
昨日のチェックをもう一歩踏み込んで確認できた。すっきり。これで、何か分かんないことがあっても多少はソースコードを調べられそう!