#:person{:first "Han"
:last "Solo"
:ship #:ship{:name "Millennium Falcon"
:model "YT-1300f light freighter"}}
Clojureには 同図像性 という特性がある。同図像性というのはClojureのプログラムはClojureのデータ構造で表現されることを高尚に説明する言葉だ。これはClojure(およびCommon Lisp)とその他のプログラミング言語との間の重要な差だ。- Clojureはストリームやファイルの中の文法 ではなく 、データ構造の評価で定義される。Clojureプログラムが他のClojureプログラムを操作や変形、あるいは創り出すということはとても一般的で、簡単に行うことができる。
とはいうものの、ほとんどのClojureプログラムはテキストファイルとして作成される。このテキストをパースし、コンパイラが解釈できるデータ構造を生成するのがreaderの役割だ。 reader とClojureのデータの表現はXMLやJSONなどを使うような文脈でも使用できる。
readerの文法は文字で定義されており、Clojureの言語はシンボル、リスト、ベクター、マップなどによって定義されていると言う人もいるかもしれない。実際にはreaderはフォームを(文字ではなく)ストリームから読み込み、そのフォームによって表現されるオブジェクトを返す read 関数によって表現されている。
どこかしらから解説を始める必要があるので、このリファレンスでは評価の最初の段階にあたるリーダーフォームの解説から始めている。これには必然的にデータ構造の詳細な解説とコンパイラによる解釈が付随する。
Symbols begin with a non-numeric character and can contain alphanumeric characters and *, +, !, -, _, ', ?, <, > and = (other characters may be allowed eventually).
'/' には特別な意味があり、シンボルの途中に含むことで名前空間とシンボルの名前を分割することができる(例: 'my-namespace/foo')。'/'単体は除算関数を指す。
'.' には特別な意味がある - シンボルの途中に含むことで完全修飾されたクラス名 (例: 'java.util.BitSet')、またはネームスペースを指定することができる。'.' で開始、または終了するシンボルはClojureに予約されている。/ か . を含むシンボルは「修飾されている」と呼ばれる。
':' で開始、もしくは終了する文字はClojureによって予約されている。シンボルには一つ、もしくは連続しない':'を含むことができる。
文字列 - "ダブルクォート" で囲まれている。複数行にまたがることもできる。標準のJavaエスケープ文字がサポートされている。
数値 - 一般的に Java と同様に表記される
整数は長さの制限がなく、Longの範囲内にある時はLongとして読み込まれ、それ以外の場合には
clojrue.lang.BigIntとして読み込まれる。末尾にNが付いている整数は常にBigIntとして読み込まれる。可能な場合には2から36の基数を指定して表記することができる(参照:
Long.parseLong())。例えば
2r101010
、8r52
、36r16
,42
は全て同じLongになる。
浮動小数点数はDoubleとして読み込まれる。末尾にMが付いているとBigDecimalとして読み込まれる。
分数もサポートされている(例: 22/7
)。
文字 - 先頭にバックスラッシュが付いている:\c
、 \newline
、\space
、 \tab
、 \formfeed
、
\backspace
、 \return
はそれぞれの文字を意味する。ユニコード文字はJavaと同じく \uNNNN
と表記される。8進数は
\oNNN
と表記される。
nil
は’何もない、もしくは値がない’ことを意味する - Javaのnullを表現し、論理的なテストではfalseになる。
ブーリアン - true
と false
Symbolic values - ##Inf
, ##-Inf
, and ##NaN
キーワード - キーワードはシンボルと似ているが、以下の点で異なる:
コロン(:) で始まらなければならない(例::fred)。
They cannot contain '.' in the name part, or name classes.
Like symbols, they can contain a namespace, :person/name
, which may
contain '.'s.
コロン2つで始まるキーワードは現在の名前空間において修飾されたキーワードに自動的に解決される:
キーワードが修飾されていない場合、その名前空間は現在の名前空間になる。 user
では ::rect
は user/rect
として読み込まれる。
キーワードが修飾されている場合、その名前空間は現在の名前空間でのエイリアスを利用して解決される。 x
が example
にエイリアスされた名前空間では、 ::x/foo
は :example/foo
に解決される。
マップは波括弧で括られた0個以上のキー/値の対: {:a 1 :b 1}
カンマは空白と同様に扱われ、マップの対を区切ることにも使える。{:a 1, :b 2}
キーと値にはどのようなフォームも使用できる。
Clojure 1.9で追加された
マップのリテラルには任意でキーに対するデフォルトの名前空間コンテキストを #:ns
プレフィックスによって指定することが可能で、その場合 ns
には名前空間の名前を指定し、プレフィックスはマップの {
の前に置く。加えて #::
を使用してキーワードの名前空間の解決と同じセマンティクスで自動的に名前空間を解決することができる。
名前空間付きのマップリテラルの文法に対する読み込みは通常のマップに対する読み込みと以下の点が異なる:
キー
名前空間なしのキーワードもしくはシンボルであるキーはデフォルトの名前空間で読み込まれる。
名前空間のあるシンボルもしくはキーワードであるキーは影響されずに読み込まれる。 ただし 、特別な名前空間 _
は読み込み時に取り除かれるので扱いが異なる。これを使用することで名前空間シンタックスのあるマップリテラルの中で、名前空間なしのキーワードやシンボルをキーとして指定することが可能になる。
キーワードもしくはシンボル以外のキーは影響を受けない。
値
値は影響を受けない。
ネストしたマップリテラルのキーは影響を受けない。
例として以下の名前空間指定付きの以下のマップ:
#:person{:first "Han"
:last "Solo"
:ship #:ship{:name "Millennium Falcon"
:model "YT-1300f light freighter"}}
は以下のように読み込まれる:
{:person/first "Han"
:person/last "Solo"
:person/ship {:ship/name "Millennium Falcon"
:ship/model "YT-1300f light freighter"}}
Javaのクラス、deftype、defrecordのコンストラクタは完全修飾されたクラス名の先頭に#を、末尾にベクターを付けることで呼び出すことができる:
#my.klass_or_type_or_record[:a :b :c]
ベクターの中の要素は 評価されずに
各コンストラクタに渡される。defrecordのインスタンスはマップを代わりに受け取る似たようなフォームを使用して作成することができる:
my.record{:a 1, :b 2}
キーを付与された値は 評価されずに defrecordのフィールドに割り当てられる。マップリテラルに対応するエントリーがないdefrecordのフィールドにはnilが割り当てられる。defrecordで定義されている以外のキーに対応する値は結果のdefrecordインスタンスに追加される。
リーダーの挙動はリーダー自身に組み込まれている定義とリードテーブルによる拡張システムの組み合わせで決定される。read tableのエントリーはマクロキャラクターと呼ばれる特定の文字に対応する、リーダーマクロと呼ばれるる読み込みの動作のマッピングを定義する。特別な指定がない限り、マクロキャラクターはユーザーシンボルに含めることはできない
上記のように文字リテラルを生成する。文字リテラルの例: \a \b \c
以下の特殊文字は汎用的に使われる文字として使用できる: \newline
, \space
, \tab
, \formfeed
,
\backspace
, and \return
.
UnicodeのサポートはJavaの規約に準拠しており、Javaのバージョンに対応するサポートがある。Unicodeのリテラルは \uNNNN
の形式となる。例えばΩのリテラルは \u03A9
になる。
メタデータは次のようなオブジェクトと関連のあるマップだ:シンボル、リスト、ベクター、マップ、IMetaを返すタグ付きリテラル、レコード、タイプ、およびコンストラクタ呼び出し。メタデータのリーダーマクロは最初にメタデータを読み込み、それを次に読み込むフォームに関連付ける。(オブジェクトにメタデータを関連付けるには
with-meta
を参照):
^{:a 1 :b 2} [1 2 3]
は {:a 1 :b 2}
をメタデータマップにもつ [1 2 3]
を生成する。
簡略化したバージョンではメタデータを単純なシンボルもしくは文字列として指定できる。その場合メタデータはキーを:tag,バリューを(解決後の)シンボルもしくは文字列とした単一エントリーのマップになる。例:
^String x
は ^{:tag java.lang.String} x
と同等。
このようなタグはコンパイラに型情報を渡すことに使用できる。
もう一つの簡略した表記ではメタデータにキーワードを使用できる。その場合、メタデータはキーをキーワード、値をtrueとした単一エントリーのマップとして扱われる:
^:dynamic x
は ^{:dynamic true} x
と同等。
メタデータはチェインすることができる。その場合、メタデータは右から左へとマージされる。
ディスパッチマクロはリーダーに、後に続く文字でインデックスされた別のテーブルに定義されているリーダーマクロを使用させる。
#{} - 上記のセットを参照
正規表現パターン (#"pattern")
正規表現パターンは 読み込み時に コンパイルされる。コンパイルした結果のオブジェクトは
java.util.regex.Patternタイプになる。正規表現のエスケープ文字のルールは文字列とは異なる。特にバックスラッシュはそれ自身として扱われる。(なのでエスケープするためにもうひとつバックスラッシュを付ける必要がない)。例えば、
(re-pattern "\\s*\\d+")
は簡潔に #"\s*\d+"
と書ける。
Var-quote (#')
#'x
⇒ (var x)
無名関数リテラル (#())
#(…)
⇒ (fn [args] (…))
+ ここで引数は%、%n
、%nまたは%&の形をとる引数リテラルの存在によって決定される。%は%1と同義で、 %nは(1から始まる)n番目の引数を指定し、%&
はrest引数を指定する。これは
fn
を置き換えるものではない - 慣習的には短く一度しか使用しないマッピングやフィルター関数などのために利用する。#()フォームをネストすることはできない。
次のフォームを無視する (#_)
#_ の後に続くフォームはreaderによって完全に無視される。(comment マクロはnilを返すのでこちらの方がより完全なフォームの除去になる)。
シンボル、リスト、ベクター、セット、マップ以外のフォームについては `xと’xは同等。
シンボルに対してシンタックスクォートはシンボルを現在のコンテキストで 解決 し、完全修飾されたシンボルを生成する。(例: namespace/name もしくは fully.qualified.Classname)。シンボルが名前空間で修飾されておらず、'#'で終わる場合、同じ名前に'_'とユニークなIDを末尾につけた自動生成されたシンボルに解決される。例: x# は x_123に解決される。同じシンタックスクォート内での同じシンボルに対する参照は同一の自動生成されたシンボルに解決される。
リスト/ベクター/セット/マップに対するシンタックスクォートは対応するデータ構造のテンプレートを設ける。テンプレートの内部では未修飾のフォームはあたかも再帰的にシンタックスクォートされたように振る舞うが、アンクォートもしくはアンクォートスプライシング修飾することでフォームを再帰的なシンタックスクォートの対象から外すことができる。その場合、フォームは式として扱われ、テンプレートの中でそれぞれその値、もしくは値のシークエンスで置き換えられる。
例:
user=> (def x 5)
user=> (def lst '(a b c))
user=> `(fred x ~x lst ~@lst 7 8 :nine)
(user/fred user/x 5 user/lst a b c 7 8 :nine)
現在リードテーブルをユーザープログラムからアクセスすることはできない。
Clojureのreaderは extensible data notation (edn)のスーパーセットをサポートしている。ednの仕様の策定は現在活動的に行われおり、Clojureのデータの文法のサブセットを定義することでこのドキュメントを言語に依らない形で補完している。
タグ付きリテラルはednのClojureによる実装 tagged elements.
Clojure が起動する際、クラスパスのルート位置から data_readers.clj
という名前がついたファイルが探される。それらのファイルには次のようなシンボルをClojureマップに書く:
{foo/bar my.project.foo/bar
foo/baz my.project/baz}
各ペアのキーはClojureリーダーによって認識されるタグとなる。ペアの値は完全修飾された Var
の名前であり、リーダーがタグの後に続くフォームをパースするために参照される。例えば上記の data_eaders.clj
ファイルが与えられた場合、Clojureのリーダーはこのフォームを次のようにパースする:
#foo/bar [1 2 3]
Var #'my.project.foo/bar
をベクター [1 2 3]
に対して呼び出す。データリーダー関数はフォームが通常のClojureデータ構造としてリーダーに読み込まれた後に呼び出される。
名前空間で修飾されていないリーダータグはClojureのために確保されている。デフォルトのリーダータグは
default-data-readers
で定義されているが、 data_readers.clj
で上書きすることもできるし、
*data-readers*
を再束縛することもできる。 タグに対してデータリーダーが見つからなかった場合、
*default-data-reader-fn*
で束縛されている関数がタグと値と共に呼びだされ値が作り出される。 *default-data-reader-fn*
がnilの場合(デフォルト)RuntimeExceptionが発生する。
Clojure 1.4では instant と UUID のタグ付きリテラルが導入された。instantは #inst
"yyyy-mm-ddThh:mm:ss.fff+hh:mm"
という形式をとる。 注意:
この形式の一部の要素は省略可能だ。詳細はコードを参照。デフォルトのリーダーは与えられた文字列をパースしてデフォルトでは java.util.Date
にする。例えば:
(def instant #inst "2018-03-28T10:48:00.000")
(= java.util.Date (class instant))
;=> true
Since
*data-readers*
is a dynamic var that can be bound, you can replace the default reader with
a different one. For example, clojure.instant/read-instant-calendar
will
parse the literal into a java.util.Calendar
, while
clojure.instant/read-instant-timestamp
will parse it into a
java.util.Timestamp
:
(binding [*data-readers* {'inst read-instant-calendar}]
(= java.util.Calendar (class (read-string (pr-str instant)))))
;=> true
(binding [*data-readers* {'inst read-instant-timestamp}]
(= java.util.Timestamp (class (read-string (pr-str instant)))))
;=> true
#uuid
タグ付きリテラルはパースされて java.util.UUID
になる:
(= java.util.UUID (class (read-string "#uuid \"3b8a31ed-fd89-4f1b-a00f-42e3d60cf5ce\"")))
;=> true
If no data reader is found when reading a tagged literal, the
*default-data-reader-fn*
is invoked. You can set your own default data reader function and the
provided
tagged-literal
function can be used to build an object that can store an unhandled
literal. The object returned by tagged-literal
supports keyword lookup of
the :tag
and :form
:
(set! *default-data-reader-fn* tagged-literal)
;; read #object as a generic TaggedLiteral object
(def x #object[clojure.lang.Namespace 0x23bff419 "user"])
[(:tag x) (:form x)]
;=> [object [clojure.lang.Namespace 599782425 "user"]]
Clojure 1.7 で複数のClojureプラットフォームの間でロードできるポータブルなファイルのための新しい拡張子 (.cljc)が導入された。プラットフォームに特有なコードを管理する主なメカニズムはプラットフォームに特有なコードをできるだけ小さなネームスペースに隔離し、それらのプラットフォーム毎のバージョン(.clj/.class or.cljs)を用意することだ。
プラットフォーム毎に異なるコードを隔離することが現実的ではない場合やコードがほとんどポータブルでプラットフォーム特有の部分がごく少ない場合のために1.7で cljcとデフォルトREPLでのみ使える リーダーコンディショナル が導入された。リーダーコンディショナル は控えめに、必要な時だけ使うことが推奨される。
リーダーコンディショナルは #?
もしくは #?@
で始まる新しいディスパッチフォームだ。どちらも cond
と似たような形でフィーチャーと式を含む。全てのClojureプラットフォームはよく知られた "プラットフォームフィーチャー" が存在する -
:clj
, :cljs
,
:cljr
。リーダーコンディショナルの条件はプラットフォームにマッチするフィーチャーが見つかるまで順番に検査され、そのフィーチャーの式を読み込んで返す。マッチしなかった分岐の式は読み込まれはするが、スキップされる。よく知られた
:default
フィーチャーは必ずマッチするためデフォルトとして使える。マッチするブランチが存在しない場合、フォームは一切読み込まれない(リーダーコンディショナルが最初から存在しないのと同等)。
以下の例はClojureでは Double/NaN ClojureScript では js/NaN、それ以外のプラットフォームではnilとして読み込まれる。
#?(:clj Double/NaN
:cljs js/NaN
:default nil)
#?@
の文法は全く同じだが、アンクォートスプライシングと同様に周りのコンテキストにスプライス可能なコレクションを返す式が期待される。リーダーコンディショナルスプライシングをトップレベルで使用することはサポートされておらず、例外が発生する。例:
[1 2 #?@(:clj [3 4] :cljs [5 6])]
;; in clj => [1 2 3 4]
;; in cljs => [1 2 5 6]
;; anywhere else => [1 2]
read と read-string 関数は任意でオプションを指定したマップを第一引数に取ることができる。このオプションマップのキーと値で現在のフィーチャーとリーダーコンディショナルの挙動を指定することができる:
:read-cond - :allow を指定してリーダーコンディショナルを処理する、もしくは
:preserve で全てのブランチを保持する。
:features - 有効なフィーチャーキーワードのパーシスタントセット。
ClojureScriptのリーダーコンデショナルをClojureからテストする例:
(read-string
{:read-cond :allow
:features #{:cljs}}
"#?(:cljs :works! :default :boo)")
;; :works!
ただ、Clojureのリーダーは 常に プラットフォームフィーチャーである:cljも注入する点には注意が必要だ。プラットフォームに依存しない読み込みに関しては tools.readerを参照。
リーダーが {:read-cond :preserve}
で呼び出された場合、リーダーコンディショナルと未実行の分岐がデータとして保持されたフォームを返す。その場合、リーダーコンディショナルは :form
と :splicing?
フラグのキーワード取得をサポートする型として返される。読み込まれたもののスキップされたタグ付きリテラルは :form
:tag
のキーによるキーワード取得をサポートする型として返される。
(read-string
{:read-cond :preserve}
"[1 2 #?@(:clj [3 4] :cljs [5 6])]")
;; [1 2 #?@(:clj [3 4] :cljs [5 6])]
これらの型の述語やコンストラクタとして以下の関数を使用することができる:
reader-conditional?
reader-conditional
tagged-literal?
tagged-literal