Clojure

JVM 上の現代的 Lisp(2007, Rich Hickey)。不変データ・関数型・並行性を中核に据えた実用 Lisp。homoiconic(コード=データ)でマクロが強力。動的型。

中核思想

homoiconic(コード=データ)

ソースコードがそのまま言語の組み込みデータ構造(リスト・ベクタ・マップ)として表現される。(f a b) は「f を a b に適用する呼び出し」であると同時に、評価しなければ「3要素のリスト」というただのデータでもある:

(+ 1 2)            ; => 3      (評価すると関数呼び出し)
'(+ 1 2)           ; => (+ 1 2) (quote するとリストというデータ)
(first '(+ 1 2))   ; => +      (データとして要素を取り出せる)

プログラムが自分のソースを通常の first/map/conj で操作できる。これがマクロを「ただのデータ変換関数」にする。

マクロの最小例

マクロはコンパイル時にコード(データ)を受け取り別のコードを返す関数。評価順を制御できる点が関数と決定的に違う:

(defmacro unless [test body]
  `(if (not ~test) ~body))   ; ` は構文quote, ~ は展開時に値を差し込む

(unless false (println "run!"))   ; => "run!"
;; マクロ展開: (if (not false) (println "run!"))

関数だと body が呼び出し前に評価されてしまうが、マクロは未評価のコードのまま受け取り if の中に織り込んでから展開する。macroexpand-1 で展開結果を確認できる。

永続データ構造(HAMT)

ベクタ・マップは内部的に HAMT(Hash Array Mapped Trie)= 分岐数32の木で実装される。「更新」は木を丸ごとコピーせず、変更経路上のノードだけ作り直し、残りは旧バージョンと共有する(path copying)。結果、assoc/conj は実質 O(log₃₂ n) ≈ ほぼ定数で、旧版もそのまま生き続ける(永続性):

(def a [1 2 3])
(def b (conj a 4))   ; b => [1 2 3 4]
a                    ; => [1 2 3](a は不変のまま、木の大半を b と共有)

これが「不変なのにコピーコストを払わない」を成立させ、ロックなしの並行性の土台になる。

並行性

可変状態の種類を明示的に分ける: atom(独立な同期更新)、ref(STM によるトランザクション)、agent(非同期)、core.async(CSP チャネル。アクターモデルとは別系統の並行モデル)。不変データゆえロックが要らない。

型と検査

動的型だが、spec / Malli で実行時のデータ契約・生成・検査を行う(静的型の代わりに「データの形」を記述)。

エコシステム

ホストは JVM(Clojure)/ JS(ClojureScript)/ .NET。REPL 駆動開発が文化。Datomic(不変DB)、フロント〜バックを同一言語で揃える構成も。

押さえどころ(カード化候補)

関連

Graph