リテラル

以下はClojureで一般的なプリミティブのリテラル表現の例だ。これらのリテラルはすべて有効なClojureの式だ。

; は行末までのコメントになる。時にはヘッダーコメントのセクションを示すために複数のセミコロンが使われることもあるが、それは慣習にすぎない。

数値型

42        ; integer (整数)
-1.5      ; floating point (浮動小数点数)
22/7      ; ratio (分数)

整数は固定精度64ビット整数として、その範囲にない場合には任意精度整数として読み取られる。末尾に N を付けることで強制的に任意精度にすることができる。Clojureはまた8進数(プレフィックスは 0)、16進数(プレフィックスは 0x)のJavaシンタックスと任意の基数(プレフィックスは基数に r を付けたもの)による整数をサポートしている。分数は分子と分母を組み合わせた独自の型として提供されている。

浮動小数点数の値は倍精度64ビット浮動小数点数として、もしくは M サフィックスで任意精度浮動小数点数として読み取られる。指数表記もサポートされている。特別なシンボルの値 ##Inf##-Inf##NaN はそれぞれ正の無限大、負の無限大、「非数」(not a number)の値を表す。

文字型

"hello"         ; string (文字列)
\e              ; character (文字)
#"[0-9]+"       ; regular expression (正規表現)

文字列はダブルクォートで囲まれ、複数行にわたることができる。個々の文字はバックスラッシュ始まりで表現する。少数の特別な名前の付いた文字がある: \newline \spec \tab など。ユニコード文字は \uNNNN もしくは8進数で \oNNN によって表現することができる。

リテラルの正規表現は # 始まりの文字列だ。java.util.regex.Patternオブジェクトにコンパイルされる。

シンボルと識別子(ident)

map             ; symbol (シンボル)
+               ; symbol - ほとんどの記号類が使える
clojure.core/+  ; namespaced symbol (名前空間付きシンボル)
nil             ; null値
true false      ; boolean (ブーリアン)
:alpha          ; keyword (キーワード)
:release/alpha  ; keyword with namespace (名前空間付きキーワード)

シンボルは文字、数字、その他の記号類で構成され、関数や値、名前空間など他のものを指し示すために使われる。シンボルはオプションで名前空間を持つことができ、名前の部分とスラッシュで区切られる。

異なる型として読み取られる特別なシンボルが3つある―― nil はnull値、 truefalse はブーリアンの値だ。

コロンで始まるシンボルはキーワードと呼ばれる。キーワードは常にそれ自身に評価され、Clojureでは列挙値や属性名としてよく使われる。

リテラルコレクション

Clojureにはまた、4つのコレクション型についてリテラルシンタックスがある:

'(1 2 3)     ; list (リスト)
[1 2 3]      ; vector (ベクター)
#{1 2 3}     ; set (セット)
{:a 1, :b 2} ; map (マップ)

これらについては後ほどもっと詳しく説明するので、今はこれら4つのデータ構造が複合データを作るのに使えることを知っていれば十分だ。

評価

次に、Clojureはどのように式を読み取って評価するのか検討しよう。

伝統的な評価 (Java)

Java evaluation

Javaでは、ソースコード(.javaファイル)が文字としてコンパイラ(javac)に読み取られ、バイトコード(.classファイル)が生成され、それがJVMにロードされる。

Clojureの評価

Clojure evaluation

Clojureでは、ソースコードが文字として リーダー(reader) に読み取られる。リーダーはソースを.cljファイルから読み取るかもしれないし対話的に一連の式を与えられるかもしれない。リーダーはClojureのデータを生成する。そしてClojureコンパイラはJVMへのバイトコードを生成する。

ここで重要な点が2つある:

  1. ソースコードの単位は Clojureの式 であり、Clojureのソースファイルではない。ソースファイルはちょうどREPLで対話的にタイプするのと同じように一連の式として読み取られる。

  2. リーダーとコンパイラの分離はマクロの余地を生む鍵となる分離だ。マクロはコードを(データとして)受け取ってコードを(データとして)出力する特殊な関数だ。マクロ展開のループがこの評価モデルのどこに挿入できるか分かるだろうか?

構造 vs セマンティクス

あるClojureの式を考えてみよう:

Structure and semantics

この図は、緑色のシンタックス(リーダーによって生成されたClojureのデータ)と青色のセマンティクス(そのデータがClojureランタイムにどのように理解されるか)の違いを示している。

シンボルとリストを 除き 、ほとんどのリテラルのClojureフォームは自分自身に評価される。シンボルは他のものを指し示すのに使われ、評価されると指し示しているものを返す。(図のような)リストは呼び出しとして評価される。

図では、(+ 3 4)はシンボル(+)と2つの数(3と4)を持つリストとして読み取られる。第1要素(+がある場所)は「関数位置(function position)」、つまり呼び出すものがある場所と呼ばれることがある。関数は呼び出すものとして明らかだが、他にもランタイムの認識する少数の特別なオペレータとして、マクロと他に一握りの呼び出し可能なものがある。

上の式の評価を考えてみると:

  • 3と4はそれ自身(long)に評価される

  • +は + を実装している関数に評価される

  • リストを評価すると3と4を引数として + 関数が呼び出される

多くの言語には文と式の両方があり、文は何らかのステートフルな効果はあるが値を返さない。Clojureでは、あらゆるものが値に評価される式だ。一部の式には副作用もある(大多数はそうではないが)。

それでは、Clojureでどのように式を対話的に評価できるか考えてみよう。

クォートで評価を遅らせる

時には評価を停止することが便利な場合もある(特にシンボルとリストについて)。シンボルが指し示しているものをルックアップすることなく単にシンボルであってほしいことがある:

user=> 'x
x

また、リストが(評価すべきコードではなく)単にデータ値のリストであってほしいことがある:

user=> '(1 2 3)
(1 2 3)

遭遇するかもしれない分かりづらいエラーのひとつは、データのリストをコードのように誤って評価してしまった結果だ:

user=> (1 2 3)
Execution error (ClassCastException) at user/eval156 (REPL:1).
class java.lang.Long cannot be cast to class clojure.lang.IFn

今はクォートについてあまり気にしすぎる必要はないが、シンボルやリストの評価を避けるためにこの資料でも時折目にするだろう。

REPL

Clojureを使う時間のほとんどはエディタもしくはREPL(Read-Eval-Print-Loop)で過ごすだろう。REPLは以下の部分でできている:

  1. 式(文字列)を読み取って(Read)、Clojureのデータを生成する

  2. #1から返ってきたデータを評価して(Evaluate)、結果(これもClojureのデータ)を生成する。

  3. それをデータから文字へ戻す変換によって結果を印字する(Print)

  4. 最初に戻る(Loop)

#2の重要な点のひとつは、Clojureは式を実行する前に常にコンパイルするということだ。Clojureは 常に JVMのバイトコードにコンパイルされる。Clojureインタプリタというものはないのだ。

user=> (+ 3 4)
7

上の囲みは、(+ 3 4)という式の評価と結果の受け取りを示している。

REPLで探索する

たいていのREPL環境は対話的な利用を支援するいくつかの仕組みをサポートしている。例えば、特別なシンボルが最後の3つの式の評価結果を記憶しておいてくれる:

  • *1 (最後の結果)

  • *2 (2つ前の式の結果)

  • *3 (3つ前の式の結果)

user=> (+ 3 4)
7
user=> (+ 10 *1)
17
user=> (+ *1 *2)
24

加えて、 clojure.repl というClojure標準ライブラリに含まれた名前空間があり、便利な関数をいくつか提供している。このライブラリをロードして現在のコンテキストで関数を利用可能にするには、以下を呼び出そう:

(require '[clojure.repl :refer :all])

今はこれをおまじないとして扱ってもよいだろう。名前空間のところに到達したら中身を説明しよう。

REPLで便利な追加の関数にアクセスできるようになった: doc, find-doc, apropos, source, dir だ。

doc 関数は任意の関数のドキュメンテーションを表示する。 + に対して呼び出してみよう:

user=> (doc +)

clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'

doc 関数は + のドキュメンテーションを有効なシグネチャとともに印字している。

docはドキュメンテーションを印字して、結果としてnilを返す――評価の出力で両方が見えるだろう。

doc をそれ自身に対して呼び出すこともできる:

user=> (doc doc)

clojure.repl/doc
([name])
Macro
  Prints documentation for a var or special form given its name

あるものがどのような名前で呼ばれるか自信がない? apropos コマンドを使って特定の文字列や正規表現にマッチする関数を見つけることができる。

user=> (apropos "+")
(clojure.core/+ clojure.core/+')

find-doc でdocstring自体も含むように検索範囲を広げることもできる。

user=> (find-doc "trim")

clojure.core/subvec
([v start] [v start end])
  Returns a persistent vector of the items in vector from
  start (inclusive) to end (exclusive).  If end is not supplied,
  defaults to (count vector). This operation is O(1) and very fast, as
  the resulting vector shares structure with the original and no
  trimming is done.

clojure.string/trim
([s])
  Removes whitespace from both ends of string.

clojure.string/trim-newline
([s])
  Removes all trailing newline \n or return \r characters from
  string.  Similar to Perl's chomp.

clojure.string/triml
([s])
  Removes whitespace from the left side of string.

clojure.string/trimr
([s])
  Removes whitespace from the right side of string.

特定の名前空間の関数の一覧が見たい場合には、 dir 関数が利用できる。ここでは clojure.repl 名前空間に対して使っている:

user=> (dir clojure.repl)

apropos
demunge
dir
dir-fn
doc
find-doc
pst
root-cause
set-break-handler!
source
source-fn
stack-element-str
thread-stopper

そして最後に、ランタイムにアクセス可能な任意の関数のドキュメンテーションだけでなく背後にあるソースを見ることもできる。

user=> (source dir)

(defmacro dir
  "Prints a sorted directory of public vars in a namespace"
  [nsname]
  `(doseq [v# (dir-fn '~nsname)]
     (println v#)))

このワークショップを進めるにあたって、使っている関数のdocstringやソースコードを気軽に調べてみてほしい。Clojureライブラリ自体の実装を探索することは、言語とその使われ方についてさらに学ぶための素晴らしい方法だ。

Clojureを学ぶ際に Clojureチートシート を開いておくのもまた素晴らしいアイディアだ。チートシートには標準ライブラリで利用可能な関数が分類してあり、非常に価値のある参考資料だ。

それではClojureの基本について考えてみよう。

Clojureの基本

def

REPLで何かを評価する時に、後のためにデータを保存しておくと便利なことがある。これは def でできる:

user=> (def x 7)
#'user/x

def は特殊形式であり、シンボル(x)を現在の名前空間で値(7)に関連付ける。このつながりは var と呼ばれる。多くの実際のClojureコードでは、varは定数値もしくは関数のどちらかを指すべきだが、REPLでは便宜的にvarを定義して再定義することが一般的だ。

上の戻り値が #'user/x であることに注意しよう――これはvarのリテラル表現だ: #' の後に名前空間付きのシンボルが続く。 user はデフォルトの名前空間だ。

シンボルは指し示すものをルックアップすることで評価されることを思い出そう。したがって単にシンボルを使って値を取り戻すことができる:

user=> (+ x x)
14

印字

言語を学ぶ際にする最も一般的なことのひとつは値を印字することだ。Clojureは値を印字するためにいくつかの関数を提供している:

人間が読める 機械が読める

改行あり

println

prn

改行なし

print

pr

人間が読める(human-readable)形式は特殊印字文字(改行やタブなど)を期待される形式に翻訳して文字列をクォートなしで印字する。 println は関数をデバッグしたりREPLで値を印字するのによく使う。 println は任意個数の引数をとって、個々の引数の印字した値の間にスペースを入れる:

user=> (println "What is this:" (+ 1 2))
What is this: 3

println関数には副作用(印字)があり、結果としてnilを返す。

上の"What is this:"は囲っているクォートが印字されず、リーダーが同じように再び読み取ることができる文字列ではないことに注意しよう。その目的では、機械が読める(machine-readable)バージョンのprnを使おう:

user=> (prn "one\n\ttwo")
"one\n\ttwo"

印字された結果はリーダーが再び読み取れる有効な形式であることに注意しよう。人間が読める印字関数、機械が読める印字関数の両者は異なるコンテキストで有用なものだ。

知識をテストしよう

  1. REPLを使って、7654と1234の和を計算しよう。

  2. 次の代数式をClojureの式として書き換えよう: ( 7 + 3 * 4 + 5) / 10

  3. REPLのドキュメンテーション関数を使って、 rem, mod 関数のドキュメンテーションを見つけよう。ドキュメンテーションに基づいて式の結果を比較しよう。

  4. find-doc を使って、最も最近のREPLでの例外のスタックトレースを印字する関数を見つけよう。