言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Clojure のネームスペースについて調べる

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 で書かれていますが、ClojureJava 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 ネームスペース シンボル)

参考