Var とは何か
Clojure のネームスペースについて調べてみました。ネームスペースを理解するには、その前に Vars について覚えておく必要があります。まず、例に使う簡単な関数を作ります。
user=> (defn hello "blah blah..." [] (print "Hello, World!\n")) #'user/hello user=> (hello) Hello, World! nil
Clojure では defn を使って関数を定義する事が出来ますが、この結果返される #'user/hello とは何でしょうか?
user=> (type #'user/hello) clojure.lang.Var
type で調べると、clojure.lang.Var というオブジェクトである事が分かります。これは Clojure のグローバル変数を特徴づけるオブジェクトで、名前と値のペアを格納するオブジェクトです。マニュアルでは Vars と読んでいます。グローバル変数の内容は、この Vars を通じて間接的にアクセスします。Vars によってエイリアスを実現する他、コメントやテストケース等のメタデータを格納するのにも使われます。
user=> (meta #'user/hello) {:ns #<Namespace user>, :name hello, :file "NO_SOURCE_PATH", :line 26, :arglists ([]), :doc "blah blah..."}
また、#'user/hello は (resolve 'user/hello) の省略形になっています。Var 自体は Java で書かれていますが、Clojure の Java API から内容を調べる事も出来ます。
user=> (. #'user/hello get) #<user$hello__60 user$hello__60@42ef83d3> user=> (. #'user/hello ns) #<Namespace user> user=> (. #'user/hello sym) hello
ネームスペースとは何か
Clojure のネームスペースはオブジェクトの一種で、Vars の集合です。だいたいキーと値の辞書だと思えば良いですが、Vars を使う事で他のネームスペースの一部をエイリアスとして持つ事が出来ます。clj シェルで使うネームスペースは *ns* という変数に格納されていて、起動時にユーザが使うのは user というネームスペースです。新しいネームスペースに入ってみます。
user=> (in-ns 'my-space) #<Namespace my-space> my-space=> (hello) java.lang.Exception: Unable to resolve symbol: hello in this context (NO_SOURCE_FILE:51) my-space=> (user/hello) Hello, World! nil
新しく作った my-space からは、先ほどの hello を直接呼び出す事が出来ません。user/hello のようにして修飾して参照します。my-space にも何か定義してみます。
my-space=> (def hoge "a new name in my space") #'my-space/hoge
ネームスペースに定義された名前を調べるのには ns-map を使います。my-space は新品のネームスペースですが、あらかじめ Java のクラスが参照出来るようになっています。
users=> (in-ns 'user) #<Namespace user> user=> (ns-map 'my-space) {ProcessBuilder java.lang.ProcessBuilder, Enum java.lang.Enum, ...
ネームスペース内だけで定義された名前を調べるのは ns-interns です。
user=> (ns-interns 'my-space) {hoge #'my-space/hoge}
あるネームスペースから参照出来る Java のクラスを imports と呼びます。
ns-imports で参照します。
user=> (ns-imports 'my-space) {ProcessBuilder java.lang.ProcessBuilder, Enum java.lang.Enum, ...
また、デフォルトネームスペースの user からも Java クラスの他に沢山 Clojure ライブラリを参照出来ます。これは imports では無く、refers と呼びます。
user=> (ns-refers 'user) {sorted-map #'clojure.core/sorted-map, read-line #'clojure.core/read-line ...
imports も refers もどちらもネームスペースを拡張する仕組みですが、Java のクラスと Clojure ライブラリで別の仕組みを使っている所が特徴といえば特徴です。これらの便利機能は ns というマクロで細かく設定出来ます。
名前を削除するのは un-unmap です。
user=> (ns-unmap 'user 'hello) nil user=> (ns-interns 'user) {}
関数一覧
引数名にネームスペースとある場所には、ネームスペースオブジェクトもシンボルも使えます。
- ネームスペースの作成
- (create-ns シンボル) シンボルの名前のネームスペースを返す。すでに無ければ作る。
- (in-ns シンボル) デフォルトのネームスペースを変更。すでになければ作る。
- (ns 名前) in-ns の高機能バージョン。
- 名前を追加
- (def 名前 値) 新しい名前と値を定義する。
- 定義されたネームスペースの検索
- (all-ns) 全てのネームスペースを返す
- (find-ns シンボル) シンボルの名前のネームスペースを返す。無ければ nil。
- ネームスペースを調べる
- (ns-name ネームスペース) 名前のシンボルを返す
- (ns-aliases ネームスペース) ネームスペースにあるエイリアス
- (ns-imports ネームスペース) インポートされた名前を返す
- (ns-interns ネームスペース) 自分自身のネームスペースの内容を返す
- (ns-map ネームスペース) ネームスペースにある内容を全て返す
- シンボルからネームスペースを探す
- (resovle シンボル) からシンボルの名前の Vars を探す。シンボルが修飾されてないときは *ns* から探す。
- 削除
- (ns-unmap ネームスペース シンボル)
参考
- Vars ソース : http://github.com/richhickey/clojure/blob/master/src/jvm/clojure/lang/Var.java
- Vars ドキュメント : http://clojure.org/vars
- ネームスペースドキュメント : http://clojure.org/namespaces