この記事は TIS Advent Calendar 2017 16 日目の記事です。
Clojure を仕事で使い始めてから三年が経ちました。未だに学びが多いですが、年数的には中堅 Clojurian と言える域に達してしまったので、自分なりの Clojure 観を整理したいと思い立ちました。自分にとって Clojure の最大の魅力は高速開発です。そしてそれは Clojure 自体の習熟難度と秤にかけても十分魅力的だという話を書きます。
どちらかと言えば Clojure 勉強し始めで、このまま進んでいいか迷っている人向けの内容です。
この記事は2017年時点での内容です。更新版・続編 → キメる Clojure チーム開発
そもそも Clojure とは
時は 2053 年、Skynet が人類を虐げる世界。エージェント Meier は Skynet のログにアクセスすることに成功し、Skynet が人類を滅ぼそうとする原因が、人類が Skynet 開発時に利用したオブジェクト指向言語とコードに散らばるミュータブルな状態にあると知った。Meier は歴史を変えて人類を救うため、エージェント Halloway を過去に送り込む。Halloway はそこで Java カップでコーヒーを飲む Rich Hickey に出会う。Halloway は Rich にさり気なく近づくと、彼の目の前に "I LOVE LISP" ステッカーを落とした...。
その数年後 Rich は新しいプログラミング言語 Clojure を開発することになる。
出典: Clojure/Conj 2017 Deep Learning Needs Clojure - Carin Meier
Clojure は一言で表すと JVM で動く LISP です。JVM 言語ゆえ実用性に富み、LISP であるがゆえの数多の魅力も持った言語です。
私と Clojure
私の3年間から Clojure 経験を抽出すると下記のような道を歩んできました。
- 2014 年
- 上司に Clojure を習得するよう指示される
- それまでのプログラミング経験は C, Java, VB.NET
- LISP...?...宇宙人?
-
Clojure 夜会 参加
- 100 人近い参加者がおり、Clojure コミュニティの盛り上がりを肌で感じた
- 今思い返すとすごい参加者でした
- 上司に Clojure を習得するよう指示される
- 2015 年
- 仕事で本格的に Clojure を使い始めることに
- 数人規模のチーム開発
- 先輩方のコードを必死に読むことでキャッチアップ
- ClojureScript なんてものもあるのか...
- 仕事で本格的に Clojure を使い始めることに
- 2016 年
- なんでもS式で書きたがる Clojure 中二病期
- ライフサイクル管理フレームワークと React.js ラッパーにはまる
- Clojure 製 OSS 開発に関わり始める
- Clojure 勉強会 開催
- 2017 年
- シリコンバレー駐在開始
- The Bay Area Clojure User Group 参加
-
Clojure/Conj 2017 参加
- LT しました
- 懇親会で Rich Hickey から「日本で Clojure カンファレンスやるなら多分行くよ」と言って貰えた
- 主に PoC 開発で、許される限り Clojure/Script を使う
現在の Clojure 技術スタック
挙げる基準がまちまちですが主にこんなものを使っています。
- ツール類
- エディタ
- Emacs
- LighTable
- Sublime Text
- フレームワーク
- Web 周り
- その他
Clojure はどんな開発に向いているか
私にとって Clojure の一番の魅力は Web の高速開発が出来る点です。そして、それは現在行っているような顧客向け PoC 開発ととても相性がいいです。
PoC 開発では当然様々な新しい技術/ソフトウェアを利用しますが、大半はそれを公開/検証する基盤として Web を選択しています。そして Clojure を使った Web 開発では、個人的な感覚では SPA によるリッチなフロントエンド、API、DB まで含んだフルスタックな Web アプリケーションであっても1スプリント以内に開発出来ます。
PoC 開発の中心は対象技術の検証なので、Web 開発にほとんど時間を取られないことに助かってます。また Clojure で作る Web アプリは検証対象技術との接続の変化にも柔軟に対応でき、立ち行かなるということは殆どありません。
「1スプリント以内で開発」というのは当然、自分が Clojure にある程度習熟しているからというのはありますが、それを差し引いても Clojure には高速開発を支える下地が揃っていると思います。
高速開発を支える Clojure の特徴
1. コードの短さ
手続き型言語と SLOC 数で比較するのはまるで意味ないですが、一般的にコードは大幅に短くなります。理由は、S式、関数型言語であること、マクロの存在などが挙げられ、頭の中で思い描いたロジックを、直接コードとして表現出来る、からだと思います。コードが短いから高速に開発できるというのはいささか短絡的ですが、頭の中に具体的なロジックが思い描けている状態での開発は非常に高速です。
後は、どれだけ言語仕様に習熟してるかに依ってきますが、習熟するに従いシンプルにロジックを表現できるようになっていきます。
(def x {:a 1 :b 2 :c [3 4]})
;; 分配束縛を使えるようになる
;; Before
(let [a (:a x)
b (:b x)
c1 (first (:c x))
c2 (second (:c x))]
[a b c1 c2])
;; After
(let [{:keys [a b c]} x
[c1 c2] c]
[a b c1 c2])
;; 適切な Macro が利用できるようになる
;; Before
(if (nil? x)
(let [{:keys [a b]} x]
[a b])
[0 0])
;; After
(if-let [{:keys [a b]} x]
[a b]
[0 0])
;; スマートにデータ更新できるようになる
;; Before
(let [{:keys [a b c]} x
[c1 c2] c
c2' (inc c2)]
{:a a
:b b
:c [c1 c2']})
;; After
(update-in x [:c 1] inc)
2. REPL
REPL は Clojure 高速開発の心臓です。
REPL を備えた言語はたくさんあり、ややもすればコードスニペット試行の便利ツール程度に扱われていますが、Clojure のそれは次元を異にします。REPL を起動するとプロジェクト中の Clojure のコードは REPL 上に読み込まれ、REPL 上から利用可能となります。理論上は REPL だけでアプリを開発することすら可能です。また、nREPL により外部から REPL への接続も可能です。
高速開発という観点ではエディタから nREPL に接続する使い方が強みです。REPL だけでアプリ開発可能と言いましたが、それが現実的でないのは REPL と実際のソースコードファイルが別だからです。エディタから nREPL に接続するとエディタで編集したソースファイル上のコードを直接 REPL に送り、実行結果を見ることが可能です。そしてそのコードに問題なければそのままファイルに保存して開発を進められます。利用するエディタのプラグインに依りますが、基本的には下記の流れで開発を進めます。
- nREPL を起動
- エディタから nREPL に接続
- REPL 上で開発する名前空間に移動
- 名前空間に対応するソースファイル上でコードを書き換え、REPL に送信
- 結果に問題なければファイルを保存
- 3 に戻る
nREPL 接続に対応しているエディタとしては Emacs, LightTable, Atom, IntelliJ, vim などがあります。Emacs を勧めすぎると老害扱いされると聞いたので敢えてお勧めしませんが Emacs(cider) を使えば Clojure 高速開発の魅力を存分に味わえるとだけ言っておきます。
REPL の問題点としては、REPL 自体の起動が遅いという点があります。プロジェクトが大きくなってくると起動に数十秒かかるということも珍しくありません。しかし、前述したように Clojure は REPL 上で開発可能な言語であるため、本来 REPL は切らずに高速に開発を進めらます。そのため、Clojurian 界隈でよく聞く言葉(自分調べ)ですが「REPL を切ったら負け」です。
また、通常 Web アプリは状態を持ち、初期化時の依存などを考慮する必要があるため REPL から扱うのが難しいですが、後述するライフサイクル管理フレームワークによりそれらも REPL から簡単に扱えるようになります。
REPL駆動開発については @lagenorhynque さんが最近より詳しく紹介してくださってました。
ClojureでREPL駆動開発を始めよう
3. 全てがデータ
Clojure では全てはデータです。具体的に言うと全ては抽象化されたマップとリストであり、Clojure コード自体もそれらで表されます。この特性は同図像性(Homoiconicity)と呼ばれ、この特性ゆえ Clojure は自身のコードを生成出来る言語でもあります(Macro)。また、マップとリストは文字列で表現でき、かつ Clojure データに戻すことが出来ます。
;; Clojure データ定義
user=> (def x {:name "test" :ids [1 2 3 4 5]})
#'user/x
user=> x
[1 2 3 4 5]
;; 文字列表現にする
user=> (pr-str x)
"{:name \"test\" :ids [1 2 3 4 5]}"
;; Clojure データに戻す
user=> (read-string (pr-str x))
{:name "test", :ids [1 2 3 4 5]}
Java ではコードスニペットがあってさえ再現するのが難しかったりします。一方 Clojure では、データは文字列として保存出来、関数はデータを受け取るだけなので、REPL にそれらを送るだけで簡単に再現できます。これはデバッグにも使え、例えば既にデプロイしているアプリに仕込んだログから、直接データを抽出して問題を再現することなども可能です。
;; 問題再現のためのログ
(log/debug (pr-str data))
コピペを推奨するものではありませんが、データとコードをコンパクトかつポータブルに扱えることで Clojure による開発は高速化します。
閉じ括弧 )))
を数えるのが辛い?Don't count. Feel. ParEdit を使いましょう。
4. ClojureScript
近年、 Web 開発ではフロントエンド開発規模の比率が非常に大きくなっています。Clojure はその完全なるサブセットとして Alt-js である ClojureScript を提供しています。
フルスタックな Web 開発では、フロントエンドと API を同時に開発することも少なくないと思います。その際、生産性の問題となるのが、二つの間で言語が変わることによるコンテキストスイッチです。Java を少し書いて JS に戻ったら、「この書き方出来ないんだっけ...?」などと関係ないところが気になって時間を費やしてしまうことなどあると思います。Clojure と ClojureScript を併用することでコンテキストスイッチを抑えることが出来、開発効率の向上が望めます。更にそれだけに留まらずコードを実際に clj/cljs で共有することさえ可能です。
また、ClojureScript には React.js のラッパーが存在し、SPA の開発も行えます。
特に om は単なる React.js ラッパーではなくフロントエンドに状態管理を持ち込んで Flux パターンの形成に影響を与えたと言われ、ver1.0 以降は om.next と呼ばれ更に多くの先進的な機能を持ちます。ただ、高速開発という観点ではよりシンプルな reagent/re-frame が勝ると思ってます。rum はすみません、使ったことないです。
ClojureScript による開発を高速化するツールとして figwheel があります。figwheel は cljs のビルドと websocket 経由でブラウザへの反映を行ってくれるツールです。上記の React.js ラッパーと組み合わせることで、SPA の動的開発を行うことができ、レイアウトの変更を即時にブラウザで確認しながら開発することができます。
- figwheel を起動しブラウザと接続
- cljs コード書き換え
- figwheel 経由で cljs ビルド
- figwheel が WS 経由で js をブラウザにプッシュ
- ブラウザから変更を確認
- 2-5 繰り返し
cljs のコンパイルはソースコードの変更を監視して自動でやる方法と後述のライフサイクル管理フレームワークに組み込んで REPL から実行する方法などがあります。js の動的読み込みはイベントハンドラの登録など、初期化順序などの問題が起きがちですが、こちらも後述のライフサイクル管理フレームワークを cljs に組み込むことで解決できます。(ayato-p さんに教えて貰いました: 参考)
5. ライフサイクル管理フレームワーク
ふさわしい呼び名がこれでいいのか分かりませんがライフサイクル管理フレームワークと呼んでおきます。
などを指しており、アプリケーションをある管理単位(component, module などと呼ぶ)に分割し、それらのライフサイクル、初期化条件、依存関係、状態などを管理するフレームワークです。どんな利点があるかと言うと、まずアプリケーション開発の大きな問題である状態の管理を簡略化できます。システムの初期化後、状態は一か所で管理され、アプリ内の関数は必要なときにそれらを引数として受け取ることで状態にアクセスすることができます。
高速開発の観点では、Web アプリのように状態とライフサイクルを持ったシステムを REPL から扱えるようになります。例えば integrant を使うと、システムを記述した config 読み込み後、下記のように関数でシステムの操作が出来るようになります。
(require '[integrant.repl :refer [halt go reset]])
(go) ;; システム起動
(reset) ;; 再起動
(halt) ;; 停止
これにより状態や他のコンポーネントへの依存を含んだ処理も REPL から扱えるようになり、開発を高速化できます。また、ライフサイクル管理に cljs-build や garden などの開発ツールを組み込んだり、SPA に組み込んで適切な順序でホットリロードさせたりという使い方も可能です。
duct
duct は integrant (以前は component) 上に、Clojure で一般的に使われる Web スタックをかぶせたフレームワークです。一般的な Web ライブラリを module として提供しているため、ボイラーテンプレートを排除できます。下記は duct の leiningen template から作成した duct の config です。
{:duct.core/project-ns test-duct
:duct.core/environment :production
:duct.module/logging {}
:duct.module.web/site {}
:duct.module/cljs {:main test-duct.client}
:duct.module/ataraxy
{[:get "/example"] [:example]}
:test-duct.handler/example
{}}
この config がシステム全体を記述しています。ring や ataraxy、default middlewares、cljs(build, figwheel) などは全て module 内に含まれているため、これらに関してコード中に明示的書く必要はありません。開発者がコーディングするべきなのは基本的に handler と client.cljs のおける実処理だけです。システム初期化後、状態は一か所で管理され、各ハンドラはリクエスト時にそれを引数に実行されることで状態(DB 接続など)にアクセスできます。
duct を使うと Web 開発はこうなります。エディタは Emacs、フロントエンドは SPA を想定しています。
- Emacs を起動し cider-jack-in で nREPL に接続
- REPL ターミナルバッファが開く
- REPL ターミナル上で
(dev)
,(go)
してシステム起動 - ブラウザで開発対象ページを開き、figwheel に接続 (自動)
- cljs ファイルを更新し REPL ターミナル上で
(reset)
- cljs-build が走り、figwheel がブラウザに通知
- レイアウト変更が反映されるのでブラウザ上で確認
- clj ファイルを開き、コードを書き換え、エディタ上で評価(
Ctrl+x, Ctrl+e
)- 評価時には編集中の clj ファイルの名前空間が反映されているため、名前空間を意識しないでよい
- 問題がなければファイル保存
- システムの状態やモジュールの初期化に関わる変更があった場合、config やコードを書き換え REPL ターミナル上で
(reset)
- 最初期化がうまくいくことを確認
- ブラウザ上からコードの変更が反映されていることを確認
- 必要なら名前空間を分割し、3-6 を繰り返し開発を進める
- 名前空間新規追加時には
(reset)
すること
- 名前空間新規追加時には
REPL だけでは難しい状態やライフサイクルも取り扱えるようになり REPL 上での高速開発を存分に味わえます。当然 REPL は切ったら負けです。
6. JVM 言語
最後に JVM 言語であることも Clojure の強みです。JVM 言語であることでどの OS 上でも動き、また Java の豊富なライブラリを直接利用できます。Clojure 製のライブラリも豊富に提供されておりそれだけで困ることはほぼないですが、必要なら Java のあらゆるライブラリが利用できるため途中で躓くことなく開発を進められます。
どんな開発に向いていないか
大規模開発?
私自身 Clojure による大規模開発の経験はありません。ただ OSS 開発時代に数人での開発は経験はあり、その時は Clojure 未経験のメンバーもいました。Clojure の開発の一番の辛さはやはりメンバー間での習熟度の差です。私のように必要に迫られて Clojure の勉強を始める人は少数派で、大多数の人は興味から始めると思います。そのため、必要なときに大量の Clojure 開発者を集めるのはやはり難しいです。
ただ、今回述べたように Clojure は高速開発に向いていると考えており、また Clojure に興味を持って始めたという人はある程度習熟した技術者であるので、Clojure 人材と銘打って募集出来れば、少数精鋭である程度までの規模の開発は行えるのではないかと思ってます。そのような募集が出来る企業であればですが。
ちなみにこちら、シリコンバレーでは Clojure 採用 はぼちぼち見かけ、有料の研修プログラムなどもあるようです。また、Conj では大学で Clojure の授業を受けたという人もいました。
性能要件のある開発
PoC 開発でシビアな性能が求められるような開発はありませんでしたが Clojure アプリは性能面ではやはり見劣りします。ただ、Web で求められる性能で言えば IO などは Java で直接書けるため問題になることはあまりないのではと思ってます。
また、Clojure の高速化に関しては @totakke さんがテクニックをまとめてくれていました。
Clojure高速化テクニック
ドメイン知識のない開発
Clojure がキマって来るとなんでも Clojure でやりたいと思い始めます。私もそうです。当然そういう人たちを否定するものではなく応援したいと思っていますが、開発速度を考慮する必要がある場合は推奨し難いです。例えば Android 開発や機械学習など、未経験であるのであればまずは特化した言語で開発してみてから re-natal や cortex を選択するべきです。先ずはこれらの知識を身に着け、その概念が Clojure に十分親和すると確信出来たら、その段階で使い始めてみるのがいいのではないかと思います。
勿論、勉強という意味ならいきなり Clojure で挑戦するのも、先例を作る意味でどんどんやって欲しいです。私も今年の Clojure Advent Calendar では Clojure で GraphQL サーバを試してみました。
終わりに
以上、長々と私の Clojure 観を高速開発という観点で述べました。
今年は日本で Clojure の当たり年でした。たくさんの勉強会が立ち上がり、多くの Clojurian が交流し、刺激を与えあっていました。日本にいなかったことが残念なくらいです。その中で新たに Clojure を始める気になった人も多いと思います。
Clojure に習熟が必要なのは事実ですが、習熟した後には Web 高速開発という武器を手に入れることができます。また、色々理由を付けてきましたが何よりも Clojure を書くのは楽しいです。是非、臆せずキメて下さい。
Clojure Advent Calendar 2017 もよろしくお願いします。