Clojureは、JVM上で動作するLisp系の関数型プログラミング言語です。2007年にRich Hickeyが公開し、不変データ構造と並行処理の扱いやすさを設計の中心に据えています。本稿のコードはすべてClojure 1.12.6 と Temurin JDK 25.0.5(macOS・x86_64)で実行し、返り値を読みやすく整形して掲載しています。版番号と実測値は2026年11月時点のものです。
まとめ:Clojureの要点
- 正体:JVM上で動くLisp方言。動的型付けで、括弧による前置記法を使い、Javaのライブラリをそのまま呼べる。
- 最新安定版は 1.12.6(2026年9月2日公開)。次期1.13は 1.13.0-alpha11 まで出ている開発段階。
- データは既定で不変。変化させたい状態だけを
atom・ref・agentに閉じ込める。refは複数の値をトランザクションでまとめて更新できる。 - コードもデータ。プログラムがリストとして読めるため、マクロで構文そのものを追加できる。
->>やwhenもマクロ。 - 1.12でJava相互運用が簡潔に。
Integer/parseIntをそのまま関数として渡せる(1.11.4ではコンパイルエラー)。 - 弱点は起動時間。同一マシンでHello Worldの実行がClojure約1.8秒、素のJava約0.2秒。スクリプト用途はBabashka(約0.1秒)で補える。
Clojureの位置づけ:JVM上のLisp方言
「クロージャ(closure)」とは別物
「クロージャー」で検索すると、言語名のClojureと、関数が外側の変数への束縛を保持する仕組みであるclosure(クロージャ)が混ざって出てきます。綴りは Closure ではなく Clojure(s ではなく j)です。Clojureの関数もクロージャを作れますが、本稿が扱うのは言語としてのClojureです。
Java APIの呼び出しとJVMの制約
ClojureはJVMのバイトコードにコンパイルされて動くため、Javaのクラスを追加ライブラリなしで呼べます。
(.toUpperCase "clojure")
;;=> "CLOJURE"
(str (java.time.LocalDate/of 2026 11 5))
;;=> "2026-11-05"
(System/getProperty "java.version")
;;=> "25.0.5"
裏返すと、メモリ構成・GC・JITの挙動もJVMのものをそのまま引き継ぎます。この部分はClojure固有の話ではないため、Java仮想マシン(JVM)とは?仕組み・メモリ・JITとインストール手順を実機で解説を参照してください。同じくJVM上で動く簡潔な言語としてはGroovyとは?JVMで動く簡潔なスクリプト言語の文法・用途・採用判断が比較対象になります。
JVM以外の実行先:ClojureScript・ClojureCLR・Babashka
同じ言語を別の実行基盤へ載せた実装があり、用途で選び分けます。版は各配布元(Maven Central・NuGet・GitHub Releases)で2026年11月5日に確認しました。
| 実装 | 実行先 | 版 | 向く用途 |
|---|---|---|---|
| Clojure | JVM | 1.12.6 | サーバー・バッチ |
| ClojureScript | JavaScript | 1.12.145 | ブラウザ・Node.js |
| ClojureCLR | .NET | 1.12.6 | .NET資産との連携 |
| Babashka | ネイティブバイナリ | 1.13.229 | シェルスクリプトの代替 |
Babashkaは公式READMEが「native Clojure interpreter for scripting with fast startup」と説明するとおり、GraalVMでネイティブ化したインタプリタです。JVMなしで起動しますが、使えるJavaクラスは同梱分に限られ、実行時に追加できません。deftype は一部対応していますが、JVM版と同じJavaクラスを生成するものではありません。また、インタプリタの実行オーバーヘッドにより、ループの多い処理はJVM版より遅くなります。手元で (clojure-version) を評価すると "1.12.6-SCI" と返り、言語仕様の版に合わせつつ別実装であることが分かります。
Clojure CLIのインストールとREPLの起動
前提はJavaです。公式のインストールガイドは、サポート対象をJavaのLTS版(8・11・17・21・25)としています。JDKの導入と切り替えはSDKMAN!の使い方|Javaのインストール・切り替えと.sdkmanrc、Windowsの手順が使えます。続けて、公式のClojure CLI(clj / clojure コマンド)を入れます。
# macOS(Homebrew)。tapの信頼登録は初回だけ
brew trust clojure/tools
brew install clojure/tools/clojure
# Linux(bash・curl・rlwrap・Javaが前提)
curl -L -O https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh
chmod +x linux-install.sh
sudo ./linux-install.sh
# 引数なしで起動するとREPLになる
clj
macOSの brew trust は、公式ガイドが初回に1回だけ必要としている操作で、古い入門記事には載っていないことがあります。Windowsは、既存の入門資料と手順を揃えやすいWSL上でLinux手順をなぞる方法を公式が推奨しており、代替としてインストーラーのclj-msiがあります。
REPLは式を1つ送るたびに結果を返す対話環境で、Clojureの開発はエディタからREPLへ式を送りながら関数単位で確かめる「REPL駆動開発」が標準です。
deps.ednによる依存ライブラリの指定
プロジェクト直下に deps.edn を置き、依存をMaven座標で書きます。clj を起動すると、ここに書いたライブラリを読み込んだREPLが立ち上がります。
{:deps {org.clojure/clojure {:mvn/version "1.12.6"}
selmer/selmer {:mvn/version "1.13.5"}
ring/ring-core {:mvn/version "1.15.6"}}}
ビルドツールとエディタの選び方
Leiningenは「CLIに置き換えられた古い道具」と紹介されることがありますが、開発は続いており最新版は2.13.0です。既存プロジェクトがLeiningenなら移行を急ぐ理由はなく、新規はCLI + deps.ednで始めるのが無難です。
| 対象 | 2026年11月時点の版 | 備考 |
|---|---|---|
| Clojure | 1.12.6 | 1.13は alpha11 |
| Clojure CLI | 1.12.5.1664 | 2026-07-27 |
| Leiningen | 2.13.0 | Codebergへ移転 |
| Selmer | 1.13.5 | テンプレート |
| Ring | 1.15.6 | ring-core |
| core.async | 1.9.865 | 1.10系は alpha |
エディタはVS Code + Calva、IntelliJ + Cursive、Emacs + CIDERが主な選択肢で、どれもREPL接続が前提です。AIアシスタントを併用する場合はECA(Editor Code Assistant)とは?Emacs・VS Codeでの導入手順とツール承認の設定で扱っています。
データ型とリテラル:キーワード・ベクター・マップ
Clojureのデータは、ほぼすべてリテラルで書けます。文字列や一部の数値にはJavaの型を使いますが、分数のRatioや任意精度整数のBigIntはClojure独自の型です。標準のコレクションにはClojureの不変な型を使います。
(map type [:name 'x "s" 1 1.5 1/3 \a])
;;=> (clojure.lang.Keyword clojure.lang.Symbol java.lang.String
;; java.lang.Long java.lang.Double clojure.lang.Ratio java.lang.Character)
(map type [[1 2] '(1 2) {:a 1} #{1 2}])
;;=> (clojure.lang.PersistentVector clojure.lang.PersistentList
;; clojure.lang.PersistentArrayMap clojure.lang.PersistentHashSet)
:name のようなキーワードはマップのキーに使う識別子で、キーワード自体が関数として働きます。マップやセットも関数として呼べるため、取り出しに専用の構文は要りません。分数リテラル 1/3 や、整数同士の除算 (/ 1 3) の結果が浮動小数点数ではなく分数(Ratio)になる点は、Javaから来ると最初に戸惑うところです。
(:name {:name "田中" :role "dev"}) ;;=> "田中"
(#{:x :y} :x) ;;=> :x
;; conj は型ごとに「効率のよい位置」へ足す
(conj [1 2 3] 4) ;;=> [1 2 3 4] ; ベクターは末尾
(conj '(1 2 3) 4) ;;=> (4 1 2 3) ; リストは先頭
;; ベクターとリストは要素と順序が同じなら等しい
(= [1 2 3] '(1 2 3)) ;;=> true
マップの並び順には注意が必要です。公式のデータ構造リファレンスは、小さなマップに使われるArrayMapの順序は変更を加えるまでしか保たれず、assoc を重ねるといずれハッシュマップに変わると説明しています。1.12.6で試すと、9要素目で切り替わりました。
(into {} (map vector [:a :b :c :d :e :f :g :h] (range)))
;;=> {:a 0, :b 1, :c 2, :d 3, :e 4, :f 5, :g 6, :h 7}
(into {} (map vector [:a :b :c :d :e :f :g :h :i] (range)))
;;=> {:e 4, :g 6, :c 2, :h 7, :b 1, :d 3, :f 5, :i 8, :a 0}
8要素までの表示順に頼ったテストや出力は、データが1件増えただけで崩れます。キーの比較順で並べるなら sorted-map を使います。挿入順を保ちたい場合は、キーと値のペアをベクターで保持するなど、別の表現を選びます。
基本構文:defn・if・スレッディングマクロ
defnによる関数定義と前置記法での呼び出し
呼び出しも制御構造も、リストの先頭に関数・マクロ・特殊形式を置く前置記法で書きます。(関数 引数 引数) の順で、Javaの f(a, b) の括弧が左へずれた形と考えると読めます。次の if は関数でもマクロでもない特殊形式です。
(defn greet [name]
(str "こんにちは、" name "さん"))
(greet "佐藤")
;;=> "こんにちは、佐藤さん"
(if false "管理者" "一般")
;;=> "一般"
-> と ->> の読み分け
処理を重ねると括弧が内側へ深くなり、実行順と読む順が逆になります。これを上から下へ並べ替えるのがスレッディングマクロです。
;; ネストした書き方(内側から実行される)
(reduce + (map #(* % %) (filter even? (range 1 11))))
;;=> 220
;; ->> で上から下へ並べ替える
(->> (range 1 11)
(filter even?)
(map #(* % %))
(reduce +))
;;=> 220
;; -> は第1引数へ差し込む(文字列・マップ向き)
(-> "a,b,c" (clojure.string/split #",") first)
;;=> "a"
違いは前の結果を差し込む位置だけです。-> は各フォームの第1引数、->> は最終引数に入れます。コア関数は、コレクションを最後に取るもの(map・filter・reduce)と対象を最初に取るもの(assoc・update・文字列関数)に分かれているため、並べる関数の側に合わせて選びます。
マクロと「コードがデータ」
Lisp方言であることの実利がここです。Clojureのソースコードは、リスト・ベクター・マップなど、言語自身のデータ構造として読めます。クォートしたコードは評価されずにデータとして残り、先頭要素を差し替えて評価し直すこともできます。
(def code '(+ 1 2 3))
(type code) ;;=> clojure.lang.PersistentList
(first code) ;;=> +
(eval code) ;;=> 6
(eval (cons '* (rest code))) ;;=> 6
マクロは、この「コードというデータ」を受け取り、別のコードを返す関数です。コンパイル前に展開されるため、関数では書けない新しい制御構造を追加できます。次の unless は、条件が偽のときだけ本体を実行します。
(defmacro unless [test & body]
`(if ~test nil (do ~@body)))
(macroexpand '(unless false (println "x") 42))
;;=> (if false nil (do (println "x") 42))
(unless false 42) ;;=> 42
(unless true 42) ;;=> nil
;; 標準の -> もマクロ
(macroexpand-1 '(-> 1 inc (* 10)))
;;=> (* (inc 1) 10)
macroexpand で展開後のコードを確かめられるので、読めないマクロに出会ったらまず展開します。ただし自作マクロは、関数で済む処理に使うと呼び出し側から挙動が読みにくくなります。新しい構文が本当に要る場面以外では、関数で書くのが原則です。
不変データ構造と並行処理の参照型
assoc による不変更新と構造共有
マップ・ベクター・セットは既定で不変です。更新に見える操作は、元の値を残したまま新しい値を返します。
(def m {:name "田中" :role "dev"})
(def m2 (assoc m :role "lead"))
m ;;=> {:name "田中", :role "dev"}
m2 ;;=> {:name "田中", :role "lead"}
毎回全体をコピーしているわけではありません。前述の公式リファレンスは「All of the Clojure collections are immutable and persistent」としたうえで、変更版の作成は構造共有(structural sharing)で効率よく行うと説明しています。変更のない部分は新旧の値で共有されます。
atom・ref・agent の使い分け
Clojureは変化する状態を言語全体に散らさず、参照型へ明示的に閉じ込めます。どれを使うかは「独立した1つの値か」「複数の値を同時に変えるか」「非同期でよいか」で決まります。
| 参照型 | 更新関数 | 使う場面 |
|---|---|---|
| atom | swap! | 独立した1つの値 |
| ref | dosync + alter | 複数の値を同時に |
| agent | send | 非同期・順番に適用 |
独立した1つの値を同期的に更新する場合は atom を使います。swap! は更新関数を受け取り、他スレッドと競合したら自動で再試行します。そのため、更新関数にはメール送信や外部API呼び出しなどの副作用を含めないでください。複数の値の整合性が要る場合は ref を使います。dosync の中の更新はソフトウェア・トランザクショナル・メモリ(STM)で1つのトランザクションになります。別のトランザクションからは整合したスナップショットを読めますが、複数のrefをトランザクション外で個別に読むと、異なる時点の値が混ざることがあります。次は100個のfutureに処理を分け、口座aからbへ1ずつ計1,000回移す例です。全処理の完了後に残高の合計が1,000であることを確認します。
(def a (ref 1000))
(def b (ref 0))
(defn transfer! [from to amt]
(dosync
(alter from - amt)
(alter to + amt)))
(let [fs (doall (for [_ (range 100)]
(future (dotimes [_ 10] (transfer! a b 1)))))]
(run! deref fs))
[@a @b (+ @a @b)]
;;=> [0 1000 1000]
;; dosync の外で ref を書き換えると例外
(alter a inc)
;; IllegalStateException: No transaction running
トランザクション外での更新が例外になるため、「ロックを取り忘れた更新」が実行時に表面化します。agent は send で渡した関数を別スレッドで順に適用し、await で完了を待てます。不変データと参照型の分担が、Clojureが並行処理に強いとされる根拠です。考え方の一般論は関数型プログラミングとは?純粋関数・不変性の基本とメリット・デメリットをTypeScriptの実例で解説で整理しています。
プロトコルとマルチメソッドによるポリモーフィズム
Clojureはクラス継承を使わずに、型や値に応じて処理を切り替えます。型で分岐するなら defprotocol、任意の値で分岐するなら defmulti です。
(defprotocol Shape (area [s]))
(defrecord Circle [r] Shape (area [_] (* Math/PI r r)))
(defrecord Rect [w h] Shape (area [_] (* w h)))
(map area [(->Circle 1) (->Rect 2 3)])
;;=> (3.141592653589793 6)
;; 既存のJavaクラスにも後から実装を足せる
(extend-protocol Shape String (area [s] (count s)))
(area "abcd") ;;=> 4
;; マルチメソッドは任意の関数の戻り値で分岐する
(defmulti fee :plan)
(defmethod fee :free [_] 0)
(defmethod fee :pro [u] (* 1000 (:seats u)))
(defmethod fee :default [_] :unknown)
[(fee {:plan :free}) (fee {:plan :pro :seats 3}) (fee {:plan :x})]
;;=> [0 3000 :unknown]
extend-protocol で String のような手を入れられないクラスにも実装を足せるのが、Javaのインターフェースとの大きな違いです。実装の無い型を渡すと No implementation of method: :area of protocol の例外になります。マルチメソッドは :plan のようなデータの値で分岐できるため、料金プランや状態遷移のように型ではなく値で振る舞いが変わる処理に向きます。
Clojure 1.12で変わったJava相互運用
1.12系の変更で既存コードの書き方に直接効くのがメソッド値(method values)です。従来は無名関数で包む必要があったJavaメソッドを、そのまま関数として渡せます。公式のリリースノートは「Programmers can now use qualified methods as ordinary functions in value contexts」と説明しています。
;; 1.11以前の書き方(1.12でも有効)
(map #(Integer/parseInt %) ["1" "2" "3"])
;;=> (1 2 3)
;; 1.12:静的メソッドを値として渡す
(map Integer/parseInt ["1" "2" "3"])
;;=> (1 2 3)
;; 1.12:インスタンスメソッドは Class/.method
(map String/.length ["ab" "abcd"])
;;=> (2 4)
同じファイルを1.11.4で実行すると、Unable to find static field: parseInt in class java.lang.Integer のコンパイルエラーになります。1.11以前は Integer/parseInt を静的フィールドの参照として解釈するためです。1.12より前を対象にした入門記事にはこの記法が出てこないので、古い記事のコードと混ぜるときは対象の版を確かめてください。
Ring + SelmerによるWebアプリのHTML生成
ClojureのWeb開発はRing仕様が土台です。リクエストのマップを受け取りレスポンスのマップを返す関数がハンドラで、テストもその関数を直接呼ぶだけで済みます。HTMLの生成にはDjango風の記法を使うテンプレートエンジンのSelmerがよく組み合わされます。
(require '[selmer.parser :as parser])
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/html; charset=utf-8"}
:body (parser/render
"<h1>{{title}}</h1>{% for p in posts %}<p>{{p}}</p>{% endfor %}"
{:title "記事一覧" :posts ["Clojure入門"]})})
(:body (handler {:request-method :get :uri "/"}))
;;=> "<h1>記事一覧</h1><p>Clojure入門</p>"
;; 変数展開は既定でHTMLエスケープされる
(parser/render "{{c}}" {:c "<script>"})
;;=> "<script>"
;; 未定義の変数はエラーにならず空文字
(parser/render "値:{{missing}}" {})
;;=> "値:"
自動エスケープが効くのはHTMLのテキストとして挿入する場合で、JavaScript・URL・イベント属性へ埋め込む場合は別の対策が要ります。|safe フィルタを付けた箇所はエスケープが外れるため、外部入力に |safe を使わないことがそのまま防御になります。未定義変数が空欄になる挙動は、テンプレートの綴り誤りを隠します。selmer.util/set-missing-value-formatter! で未定義時の出力を差し替えられるので、開発時だけ例外を投げる設定にしておくと早く気づけます。
Clojureを選ぶべきでない場面
起動時間が課題になる用途には向きません。同一マシン・同一JDK(Temurin 25.0.5)で、Clojureは clojure.main からソースを実行し、Javaはjavac済みのクラスを、Babashkaはネイティブバイナリでソースを実行しました。プロセス開始から終了までを time -p で4回ずつ測り、初回をウォームアップ扱いで除外し、2〜4回目を示した結果が次の表です。初回との差がディスクキャッシュに由来するかは切り分けていません。
| 実行内容 | 2回目 | 3回目 | 4回目 |
|---|---|---|---|
| Clojure 1.12.6 | 1.81秒 | 1.80秒 | 1.79秒 |
| 素のJava(javac済み) | 0.21秒 | 0.21秒 | 0.23秒 |
| Babashka 1.13.229 | 0.10秒 | 0.10秒 | 0.10秒 |
Clojureの実行は素のJavaより約1.6秒長くかかりました。初期化・ソースの読み込み・コンパイルを個別には測っていないため、差の内訳は特定できません。長時間稼働するサーバーなら起動コストは償却できますが、コマンドを叩くたびにプロセスが起動するCLIツールや、コールドスタートが課金・レイテンシに直結する短命なサーバーレス関数では、起動コストが応答時間の制約になり得ます。ただし、この約1.6秒の差は掲載環境での測定値であり、サーバーレス環境での差を示すものではありません。スクリプト用途ならBabashka、JVM版の機能が要るならGraalVMのNative Imageでネイティブ化する手があり、後者はGraalVMとは?JVMとの違いとNative Imageの作り方【2026年版】で手順を扱っています。どちらも避けたいなら、別言語を選ぶほうが早く済みます。
採用と引き継ぎの制約も現実的な論点です。State of Clojure 2025では回答者の82%が6年以上の職業プログラミング経験を持ち、約3分の2がClojureを主言語にしています。この調査は回答者の経験年数や主言語を示すもので、Java・Kotlinとの採用難易度の比較ではありません。採用判断では、募集地域と条件に合う候補者数を別途確認する必要があります。Clojureを書ける人が1人だけで、引き継ぎ資料も代替の担当者もいないなら、その人の離脱で保守が止まります。レビューできる人を2人以上確保できるかを先に確認してください。
同じ調査は、業種の上位を「Fintech, Enterprise Software, and Healthcare」で合計51%超と報告しています。同調査は大規模組織の回答者の多くをNubank(数千人のClojure開発者を抱えるデジタル金融)と見ており、2020年7月にはClojureの開発を担ってきたCognitectが同社グループに加わりました(公式発表)。言語の継続性という点では、特定企業に支えられていることが安心材料にも依存先にもなります。
逆に、データ変換のパイプラインが処理の中心にあり、共有状態と並行処理がバグの温床になっている領域では、不変データとSTM、REPLでの逐次確認が効きます。既存のJava資産を残したまま部分的に導入できるので、独立したバッチやデータ加工から試すのが現実的な入り口です。言語選定から含めた業務システムの開発は、フルスクラッチ開発で相談を受け付けています。
よくある質問
Clojureとクロージャ(closure)は同じものですか?
別物です。Clojureは2007年に公開されたJVM上のプログラミング言語の名前で、closure(クロージャ)は関数が外側の変数を保持する仕組みを指す一般用語です。綴りも Clojure と closure で異なります。
Clojureの最新バージョンはいくつですか?
2026年11月時点の最新安定版は1.12.6(2026年9月2日公開)です。次期の1.13は 1.13.0-alpha11 まで公開されている開発段階なので、本番では1.12.6を使います。
ClojureとClojureScriptの違いは何ですか?
言語はほぼ同じで、出力先が違います。ClojureはJVMのバイトコードに、ClojureScript(最新版1.12.145)はJavaScriptにコンパイルされ、ブラウザやNode.jsで動きます。Javaのクラスを直接呼べるのはClojure側だけです。
ClojureとJavaはどう使い分けますか?
同じJVM上で共存できるので、二者択一にする必要はありません。Javaのライブラリはそのまま呼べ、逆に gen-class でJavaから呼べるクラスも生成できます。既存のJavaシステムは残し、データ変換や並行処理の多い部分だけをClojureで書く分担が現実的です。
Clojure開発はVS Codeだけで足りますか?
足ります。拡張機能のCalvaを入れれば、REPL接続・式の評価・補完を使えます。コードを動かすにはJDKとClojure CLIも必要です。IntelliJ + CursiveやEmacs + CIDERも同等の機能を持つので、普段使いのエディタに拡張を入れる方針で問題ありません。