何故、RingハンドラーにVarを渡すと、ハンドラーを書き換えても書き換えたハンドラーが呼び出されるのか
昔どこかに書いたような気もするけど、パッと思い出せなかったので書いてみる。
例えば、Clojureのwebアプリのサンプルコードとかで以下のようなコードをよく見かける(それを日本語で広めてる諸悪の根源は僕な気がするけど…)。 code:clojure
これの疑問点は何故以下のように直接関数を渡さずに、Varを渡すのかということだろう。また、何故Varを渡すとハンドラー(この場合handler)を書き換えてもそれが反映されるのか、あるいは何故Varを渡さないと反映されないのか、ということにあると思う。
code:clojure
(run-jetty handler {:port 3000})
これらの疑問に答えるのはそんなに難しくなくて、以下のポイントを押さえていればなんとなく分かると思う。
シンボルは評価されるとネームスペース内で対応するVarオブジェクトを探し、そのVarオブジェクトを束縛している値を返す
Varオブジェクト自身はミュータブルなオブジェクト
つまり、Varオブジェクトを束縛する値は変わる
VarはIFnを実装しているため関数として扱うことができる
その場合、Varオブジェクトをそのとき束縛している値を関数として呼び出す
以下のサンプルコードを実際に試してみると言ってる意味がわかると思う(たぶん)。
code:clojure
;; ただ:fを返すだけの関数f
(defn f [] :f)
(f)
;; => :f
;; Varは関数として扱える(IFnを実装しているため)
(#'f)
;; => :f
;; 任意の関数を受けとって、内部的にそれを利用する新しい関数を返す関数g
;; gにfを渡して新しい関数g'を作る(シンボルfを束縛していた関数オブジェクトが渡されている)
(def g' (g f))
;; gにfのVarを渡して新しい関数g''を作る(Varオブジェクトそのものが渡されている)
;; g'を呼び出すと当然:fが返る
(g')
;; => :f
;; g''も同じ
(g'')
;; => :f
;; 一度fを書換えてみる
(defn f [] :new-f)
;; もう一度(g')を評価しても:fが返される
(g')
;; => :f
;; (g'')は変わっている
(g'')
;; => :new-f
つまり、run-jettyにVarを渡している例だと、この渡しているVarオブジェクトを束縛しているものが変わったとしても、Varを辿って関数(ハンドラー)を参照できる。
code:clojure
しかし、このシンボルを渡している例だと、この時点でのVarオブジェクトを束縛している値が渡されているので、ハンドラーを書き換えても反映されないというわけです。
code:clojure
(run-jetty handler {:port 3000})