GPソフト Wiki - ClojureのVarの記事を読んでいて、 自分が、Clojureのdefの挙動やSymbol, Varといった言葉の意味をよく理解していないことに気付きました。この記事や以下の記事を参考にしつつ自分なりに一度整理してみます。

clojure.lang.Var について知っていること、分かったこと

なんらかの「もの」を表すオブジェクト。名前はない（名前の機能は Symbol が独立して担当）。

可変（mutable）。Clojureで可変なのは他に、 Refs 、 Agents 、 Atoms だけ。*1

def によって、なんらかのオブジェクトを束縛した状態で作成され、名前空間にinternされる。

Clojureの Var に似た機能をJavaのなかに探すとしたら「変数」だろうか。

Varという名前は、variableからきていると思っている。

clojure.lang.Symbol について知っていること、分かったこと

Rubyのシンボルとはかなり違う。Rubyのシンボルに近い用途としてClojureでよく使うのは Keyword 。

「もの」の名前を表すオブジェクト。

Clojureの Symbol に似た機能をJavaのなかに探すとしたら「変数名」だろうか。Javaの場合は「変数」と「変数名」を切り離して捉えたりはあまりしないが、Clojureではこの「変数名」の役割を Symbol というオブジェクトに独立して担わせている。

Symbol を評価すると、まずその名前に対応する Var を調べ、そして Var が束縛する値になる。対応する Var が見つからない場合は、 RuntimeException が投げられる。

user=> a CompilerException java.lang.RuntimeException: Unable to resolve symbol : a in this context, compiling: ( /private/var/folders/sf/lyhc_rw16xlckx_yj0gyh5c40000gn/T/form-init1896185904742541191.clj:1:13012 )

user=> (def a 123 ) #' user/a user=> a 123

Symbol はインターフェイス IFn を実装している。つまり関数として振る舞うことができる。

具体的には、コレクションを１つ引数に取り、その Symbol のキーに対応するバリューを返す。

user=> (' a {' a 10 ' b 20 }) 10

def について知っていること、分かったこと

defによるbind（束縛）

オブジェクトを束縛する単純なコードです。

user=> (def foo "hoge" ) #' user/foo

このコードでは

まず、 "hoge" という String オブジェクトが生成される。*2 次に、 Var オブジェクトが作成される。 このとき、この Var オブジェクトは "hoge" オブジェクトをbind（束縛）する。 そして、 foo という名前の Symbol オブジェクトが作成され、 それから、この Var オブジェクトをintern（拘禁）する。

具体的には、 Symbol と Var のマッピングを、 Namespace （名前空間）に登録している。

( 4 ) Symbol ( 2 ) Var ( 1 ) String +------------+ +------------+ +------------+ | | | | | | | foo |-------------->| 0x4e082766 |------------>| hoge | | | ( 5 ) intern | | ( 3 ) bind | | +------------+ +------------+ +------------+ Namespace +-------------------------------+ | +-----------------------+ | | | [Symbol] : [Var] | | | | ... : ... | | | | ... : ... | | | | foo : 0x4e082766 | | ( 5 ) intern | | ... : ... | | | +-----------------------+ | | | +-------------------------------+

上記の図のとおり、internの対応は Symbol オブジェクトが保持しているわけではなくて、 Namespace （名前空間）オブジェクト内のフィールドにマップで保持されています。面倒なので、以降の図ではマッピングの図は省略して単に Symbol から Var に矢印を引きます。

このような挙動なので、多くの他の言語では var x = 1 の結果を「xは1」と表現するのに対して、Clojureでは同様の (def x 1) というコードは「シンボルxと名前が付けられたvarの値が1」と表現するとやや厳密になります。

defによるunboudなVar

def の第二引数は Var がbindする初期値です。これを省略すると何もbindしない（unboundな）状態の Var がinternされます。 *3 *4

user=> (def bar ) #' user/bar

Symbol Var Var$Unbound +------------+ +------------+ +------------+ | | | | | | | foo |-------------->| 0x4e082766 |------------>| | | | intern | | bind | | +------------+ +------------+ +------------+

この状態で bar を評価するとunboudな Var$Unbound というオブジェクトが返ってきます。

user=> bar # object [ clojure.lang.Var$Unbound 0x62d56f27 "Unbound: #'user/bar" ] user=> ( class bar ) clojure.lang.Var$Unbound

ちなみに、自分はこのときの Var は何もbindしていないのかと思っていたのですが、実際には上記の図のように clojure.lang.Var$Unbound クラスのオブジェクトをbindしているようです。 この clojure.lang.Var$Unbound は、 clojure.lang.Var クラス内に書かれたインナークラスであり*5、コードを読むと clojure.lang.Var への参照のフィールドを持っています。

上記の図よりも下記の図のほうが、実際の実装により近いかもしれません。

Symbol Var Var$Unbound +------------+ +------------+ +------------+ | | | |------------>| | | foo |-------------->| 0x4e082766 | | | | | intern | |<------------| | +------------+ +------------+ +------------+

(1) そのSymbol自体の取得

その Symbol オブジェクト自体を取得するには quote 特殊フォームを使います。

user=> (quote foo ) ) foo

これを取得する | | V Symbol Var String +------------+ +------------+ +------------+ | | | | | | | foo |-------------->| 0x4e082766 |------------>| hoge | | | resolve | | | | +------------+ +------------+ +------------+

' リーダー・マクロでも同じ効果が得られます。

user=> ' foo foo

symbol 特殊フォームを使うとSymbol名を文字列で指定して取得できます。

( symbol "foo" ) foo

'foo の型を確認すると、確かに clojure.lang.Symbol です。

user=> ( class ' foo ) clojure.lang.Symbol

(2) intern（拘禁）されているVarの取得

Var オブジェクト自体を取得するには var 特殊フォームを使います。

user=> (var foo ) #' user/foo

これを取得する -----------------------¬ | V Symbol Var String +------------+ +------------+ +------------+ | | | | | | | foo |-------------->| 0x4e082766 |------------>| hoge | | | | | | | +------------+ +------------+ +------------+

#' リーダー・マクロでも同じ効果が得られます。

user=> #' foo #' user/foo

#'foo の評価結果の型を確認すると、確かに clojure.lang.Var です。

user=> ( class #' foo ) clojure.lang.Var

(3) bind（束縛）されている値の取得

def でbindした "hoge" オブジェクトを取得するには、 foo という Symbol を評価します。

user=> foo "hoge"

まず、 foo という Symbol に対応する Var を調べます。これを「resolve（解決）する」と表現するようです。 対応する Var が見つかったら、その Var がbindする値を取得する。 この場合、得られる値は hoge 文字列オブジェクトです。

これを取得する --------------------------------------------------¬ | V Symbol Var ( 3 ) String +------------+ +------------+ +------------+ | | | | | | | foo |-------------->| 0x4e082766 |------------>| hoge | | | ( 1 ) resolve | | ( 2 ) | | +------------+ +------------+ +------------+

foo の評価結果の型を確認すると、確かに java.lang.String です。

user=> ( class foo ) java.lang.String

また、 @ リーダーマクロは Var にbindされている値を取得できます。したがって、 @ と #' の２つのリーダー・マクロを組み合わせて "hoge" を得ることもできます。（この場合は読み辛いだけですが。）

user=> @#' foo "hoge"

まとめ

def は第二引数の値を第一引数の Symbol にintern（拘禁）する。 Symbol を評価すると、その Symbol にintern（拘禁）されている Var にbind（束縛）されている値が返る。

そして、長くなってきたので続きます。

（追記2016年08月30日）

こちらのまとめもとても勉強になりますので、ぜひご参照を。