- 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日)
こちらのまとめもとても勉強になりますので、ぜひご参照を。