- def と Symbol と Var の話(この記事)
- def と Symbol と Var の話 2:なぜ Symbol と Var が独立して存在するのか
- def と Symbol と Var の話 3 :値の変更
GPソフト Wiki - ClojureのVarの記事を読んでいて、 自分が、Clojureのdefの挙動やSymbol, Varといった言葉の意味をよく理解していないことに気付きました。この記事や以下の記事を参考にしつつ自分なりに一度整理してみます。
- Clojure - Vars and the Global Environment
- Explain Clojure Symbols - Stack Overflow
- Difference between Symbols and Vars in Clojure - Stack Overflow
clojure.lang.Varについて知っていること、分かったこと
なんらかの「もの」を表すオブジェクト。名前はない(名前の機能は
Symbolが独立して担当)。defによって、なんらかのオブジェクトを束縛した状態で作成され、名前空間にinternされる。Varという名前は、variableからきていると思っている。
clojure.lang.Symbolについて知っていること、分かったこと
「もの」の名前を表すオブジェクト。
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を実装している。つまり関数として振る舞うことができる。
具体的には、コレクションを1つ引数に取り、その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されている値を取得できます。したがって、@と#'の2つのリーダー・マクロを組み合わせて"hoge"を得ることもできます。(この場合は読み辛いだけですが。)
user=> @#'foo "hoge"
まとめ
defは第二引数の値を第一引数のSymbolにintern(拘禁)する。Symbolを評価すると、そのSymbolにintern(拘禁)されているVarにbind(束縛)されている値が返る。
そして、長くなってきたので続きます。
(追記2016年08月30日)
こちらのまとめもとても勉強になりますので、ぜひご参照を。