Jepsen が Clojure で書かれた理由
(aphyr.com)- Jepsen はデータベースのような並行システムを検証するツールであるため、実装言語も並行性モデルと実際のスレッド対応が重要な選定基準だった
- Clojure の不変・永続データ構造と JVM の並行処理ツールは、テストコードのエラー可能性を減らし、Java ベースのデータベースクライアントとも相性がよい
- Jepsen の作業は決まった製品開発というより実験と探索に近く、REPL、マクロ、threading macro、EDN などの Clojure の機能が複雑なデータ処理に有利だった
- 性能は最上位ではないが、慣用的な Clojure は通常 Java より1〜2桁以内で遅い程度であり、必要な箇所は最適化と JVM のプロファイリングツールで対処できる
- 小さなコミュニティ、広く成功した静的型システムの不在、primitive の扱いにくさ、エラーメッセージなどの弱点はあるが、1〜3人が保守・利用する Jepsen には受け入れ可能なトレードオフだった
並行性テストツールに適した言語条件
- Jepsen は並行システムをテストするために作られ、テスト対象のほとんどはデータベースである
- Clojure は並行性テストコードを書く際に必要な基本条件をよく満たしている
- 不変・永続データ構造が、共有状態を扱うコードのエラー可能性を下げる
- 実際のスレッド、promise、future、atom、lock、queue、cyclic barrier、
java.util.concurrentなどを利用できる - Haskell のように副作用の制御がより厳格な言語も検討したが、Clojure のより教条的でないアプローチのほうが Jepsen には合っていた
- データベーステストには、さまざまなクライアントとの接続が必要になる
- ほぼすべてのデータベースは通常 Java で書かれたJVM クライアントを提供している
- Clojure は Java との相互運用性が高く、こうしたクライアントを活用しやすい
実験とデータ探索に有利な Clojure の機能
- Jepsen のテストは実験的な作業なので、簡潔で、変更しやすく、プロトタイピングに適した言語が必要だった
- Clojure の簡潔な文法とマクロシステムは、このような作業スタイルによく合っている
- threading macro は連鎖的な変換を読みやすくする
- マクロにより、再利用可能なエラー処理とリソーススコープ制御を実装しやすい
- Clojure REPL は、テスト実行結果のデータをその場で探索するのに役立つ
- テスト過程では、複雑でネストしたデータ構造を表現・変換・検査する必要がある
- Clojure のデータ構造と標準ライブラリ関数はこの作業に強い
- コンソールやファイルに構造化データを大量に出力する際、EDN データ構文がよく合う
性能と長期保守のトレードオフ
- Jepsen は非常に大きなシステムではないが、適度な量のデータを扱うため、「十分に良い」性能が必要である
- Clojure は最速の言語ではない
- 慣用的な Clojure は通常、Java との差が1〜2桁以内の性能差にとどまる
- 重要なボトルネックでは差を縮められる
- JVM のプロファイリングツールは Clojure とよく連携する
- Jepsen は約10年続くプロジェクトなので、成熟した中核と安定性が重要だった
- Clojure は JVM ターゲットとしても言語自体としても安定している
- ライブラリが Scala や Ruby ほど速く「腐る」ことがない
受け入れる必要がある Clojure の弱点
- Clojure には Jepsen の規模では対応可能だが、より大きなチームには負担になり得る欠点がある
- エンジニアリングコミュニティが小さい
- 広く受け入れられ成功した静的型システムがない
- Jepsen は一度に1〜3人だけが保守・利用するため、この制約の影響は比較的小さい
- JVM primitive を扱うとき、Java に降りないと不便に感じることがあり、実際にときどき Java を使う
- 多相性システムの一部は不足しているがライブラリで回避でき、エラーメッセージもよくない
- Jepsen は複数の言語でプロトタイプを作った後に Clojure に決まり、10年が経った今でもかなり良いトレードオフであり続けている
1件のコメント
Hacker News のコメント
Clojure/ClojureScript で SaaS を10年ほど運用してきた立場として、Kyle の記事には全面的に同意する
さらに価値が大きかったのは、ドメインコードの大半を cljc ファイルに置いてサーバーとクライアントの両方にコンパイルできること、transducer と transducer パイプラインが性能・合成性・再利用性の面で過小評価されていること、そして Rich Hickey が設計した言語らしく安定性と長期互換性に優れていること
後方互換性は非常に重要で、絶えず変わるツールに振り回されるよりアプリケーションに集中できるし、平均的な Clojure プログラマーが12年以上の経験を持ち、報酬も上位四分位に入っているのも偶然ではないと思う
ほとんど壊れず、アップグレードも簡単。HMR、REPL、cljc ファイルのおかげで開発者体験も素晴らしく、Clojure のコードは書いていて楽しい
動くものを壊さないために言語やシステムを無理に曲げるのはよいと言いながら、実際に稼働しているソフトウェアの大きな部分を支えている2つの言語には、輝かしい過去と現在を断ち切ることを期待する理由が分からない
Clojure の最大の問題は間違いなくエコシステム
アクセス可能で使えるツールの分布を見ると、左端に寄りすぎていてひどいが手は届きやすいツールか、右端に寄りすぎていて README に『指輪物語』の引用があり、すべてが n 次までデータで、目隠しされて逆さ吊りにされた状態で Emacs プラグインを書けるドラゴンだけが扱えるようなものか、という感じ
言語自体は素晴らしく、Java 相互運用性も Aphyr が言うよりはるかに良いが、Clojure に Rails や Django、もっと良くは Phoenix のようなものがあったなら、ずっと説得しやすく、もっと広く使われていただろうと思う
とりわけ Algol 系の命令型言語から Lisp へチームを移すよう説得するには、得られるものが苦労を上回る必要があるが、現状では HTTP ルーティングのような基本要素を除くと、ほぼすべてのプロジェクトで車輪の再発明が必要になる
それでも、どんな Maven パッケージでも使え、jar にコンパイルされるので、jar が動く場所なら Clojure も動くというのは非常に大きな利点
https://calva.io
Clojure には複数の形態があり、主に Clojure と ClojureScript が使われ、Babashka、Scittle などもあるが、いずれも clj や cljs から派生している
開発ツールはプラットフォームと IDE の好みによって異なり、Emacs、Neovim、VSCode、IntelliJ ごとにツールが違い、ビルドシステムも必要に応じて異なる
clj では deps.edn が現在最も多く使われ、その次に leiningen が続き、cljs には shadow-cljs のほかにも複数の選択肢がある
初めて触れる人には圧倒されるかもしれないが、コミュニティは良く、いつも助けてくれる。いくつもの言語で仕事としてプログラミングしてきたが、Clojure が断然いちばん気に入っている
2013年ごろから Clojure を使っているが、そういうツールは思い浮かばない。人気のあるライブラリの大半は優れていて、安定性・後方互換性・単純性の面で、自分の知るほとんどのエコシステムよりずっと先を行っている
Clojure と ClojureScript の両方で共有できる .cljc ソースファイルのおかげで、同じ Clojure コードをフロントエンドとバックエンドの両方で実行できる
Rich Hickey が言うように、JVM だけが「届く」のではなく、JavaScript にも届く
Emacs は長く使ってきており、望むように設定したり必要な elisp コードを最終的に作ったりはできるが、elisp が得意だとはまったく言えない
Lisp 方言をまったく学んだことのない人が Emacs、elisp、Clojure を同時に学ぶのは難しく、説得も非常に大変だというのは認める
今では Clojure LSP サーバーがあり、Emacs だけでなく他の IDE でも動作するため、多くの Clojure 開発者が Emacs を使っているとしても、Clojure 開発に Emacs が必須というわけではない
7年間 Clojure だけを使ってきて、50万行を超える Clojure + ClojureScript コードベースを積極的に扱っているが、堅牢な型システムがないことが最大の不満
Clojure は表現力が高く、素早く進められ、邪魔をしないという点には同意するが、再現やデバッグが難しい厄介なランタイムエラーも許してしまう
広く受け入れられ成功した静的型システムがあれば、Clojure はすごいものになっていただろう。現状では、Ruby や Python が好きで、より強力な何かを求めるならかなり良い選択、という程度だと思っており、今は Go が正しい道なのか考えるようになっている
Clojure(Script) は本当に祝福に近いもので、小さなものから大きなものまで、多くのプロジェクトにとって素晴らしい選択肢だという点には完全に同意する
C、Python、PowerShell、Java、Bash、SQL を大学の課題やシステム管理、トラフィックダンプ解析などで少しずつ使ったあと、初めて本格的にプログラミングした言語が Clojure だった
OrgPad を Clojure+ClojureScript で作っていて、コードが10万行を超えても良い判断だったと思っている
とっつきやすく、少しコードを書けば、自分が想像した通りに動くはずだという確信がかなり持てる。Python、PowerShell、Bash でもここまでではなく、まったく別格だと感じる
REPL のワークフローも本当に良く、同僚が関連動画を作っている: https://www.youtube.com/watch?v=4igO7Qbyj9o
最近は、Bash から Python に移るべきか迷うような関数、短い作業、小さなプロジェクトも Babashka/nbb で処理できる
以前 Clojure と ClojureScript を数千行ほど書いてみようとしたとき、最大の問題は、ツールの大半がかなり雑なエラー状態を持っていることだった
Python にも似た面があり、たいていの Python プログラムを Ctrl+C で止めると、よく分からない内容が混ざったスタックトレースが出るが、始めたばかりの立場では先に進むのが難しかった
言語とエコシステムには革新的で興味深いアイデアがたくさん入っていると思う。少なくとも Clojure の歴史に関する論文を読み、教訓の大半を自分のものにする価値はある
みんなには異端に聞こえるだろうが、Clojure ほど性能とコード作成の ergonomics に真剣で、それでいて括弧のない スクリプト言語 が入り込める小さなエコシステム上の余地があると思う
nil を特別扱いする慣習もなくてよいと思うが、少なくともそこには多少の根拠はある
(f x y)がf(x, y)より良いのは分かるが、自分の問題は let フォームがインデントを生む場合に近い。値に名前を付けるために、インデントと括弧のコストを同時に払う必要はないと思うPython のように、どうせコードに入れるインデントを情報として使う言語から来ると、こういうものが単なるノイズのように感じられることがある
関数型スタイルでは、関数間で渡される中間引数に名前を付ける必要はあまりない
たいていはスレッディングマクロの
->、->>で引数を次の関数へ暗黙に渡すか、変換そのものを純粋関数として定義してトップレベルに置くただし、一部の相互運用コードでは let が多く必要になるのは分かる。特に手続き的なチェーンの中で引数を変更する Java の JWT ライブラリがそうだ
しばらくデバッグしてから、再ロードや再初期化をしていなかったことに気づく、というようなことだ
たいていは「そんなはずはない」という状況で確認するリストに、それを追加するようになるのだと思う。例えば「自分のコード変更が実際に呼ばれているか確認するためにデバッグ出力を入れよう」といったことだ
もちろんこれは安全網にすぎず、主に別の REPL 作業習慣でそうした状況を避けようとしている
(+ clojure clojurescript)と読まれるべき気がして、頭の中がつらくなるClojure は多くのことを正しくやっていて、だからとても好きだ
実際のチームが実際の Web 作業を Clojure で作るときに速度を落とす要因の一つは、それぞれ方式の違う大量のライブラリを組み合わせて、すべてをつなぎ合わせようとする、ほとんど教条的な欲求だと思う
コミュニティが現在のアプローチに加えて、積極的にメンテナンスされ、バッテリー同梱で、一般的に使いやすい Web フレームワーク も備えれば、Clojure はずっと人気が出て、使いやすくなると思う。Biff がその役割なのか気になる
言語は好きだが、自分で怪物を組み立てるようなやり方は好きではない人にも、選べる別の道があるとよい
Ruby on Rails に慣らされているせいもある
有名な Rich Hickey の講演のように、complect せず compose せよ、というアプローチだ: (https://github.com/matthiasn/talk-transcripts/blob/master/Hi...)
Clojure とグラフデータベース、場合によっては HTMX まで学ぶのはかなり多い。それでも多くの設定はすでに済んでいて、認証は丁寧で簡単、サーバー設定ファイルのコメントもよく書かれている
Linux サーバーを設定し、本番 REPL に SSH で入るデプロイスクリプトもある。XTDB を使いたいなら、Biff のドキュメント検証の設定方法は本当に良い
個人的には、ClojureScript で動くアプリよりこちらのほうが好きだ。React を包んだ ClojureScript ラッパーは多く、ほとんどミームのようになっているが、React 開発者として、そのどれも React より簡単だとは感じなかった
むしろ複雑で手になじまない感じがする。一方で HTMX/Hyperscript は、HTML 構文が操作しやすい配列とオブジェクトにすぎないので、Clojure と非常によく合う
Clojure 愛好家たちが繰り返す「コードはデータだ」という格言が、初めて実感を伴って感じられた
以前 OCaml を何度か少しずつ使ってみようとしたが、あまりに厳格な型システムが好きになれなかった
コードを書く時間より、コンパイラを満足させる時間のほうが長かった。Clojure、Common Lisp、C では、コンパイラや型システムを自分の望む方向に曲げる自由がより大きい
もちろん長所と短所はあるが、コードを書くときに邪魔されたくない。自分のスタイルなのかもしれないが、同じ考えの人にも多く会ってきた
静的型システムは好みの問題だと信じていて、反対側の陣営は違う考え方をする。それでも相反する陣営があると、互いから多くを学べるので良い
Rich Hickey は、Clojure はホスト言語なので、必要なら Java や JavaScript に降りても構わない、と何度も言っていた
個人的には、これが Clojure エコシステムの最も強力な機能の 1 つだと思う。はるかに実用的だからだ
「あの汚い別の言語で書かれたライブラリ」に触りたくないという理由で、同じものを好きな言語で何度も再実装しようとする試みを多く見てきた。遅い、安全でない、柔軟でないなど、理由はさまざまだ
こうしたことは Common Lisp でも、Rust コミュニティでもよくあったし、今もある
SBCL がその宣言をできるだけ保証してくれることで、プロジェクトごとにおそらく数分ずつ頭痛を減らしてくれたはずだ
そして CL は非常に実用的なので、たとえば woo が C ライブラリベースであることについて、誰もまったく気にしない
運用上の問題はすべて、小さく微妙な問題に関係していた。ランタイムのアップグレードで JSON キーの順序が変わり、JSON を手動で展開していた古いクライアントが壊れたり、スタックの奥深くにあるライブラリが HTTP コードを文字列ではなく整数で返し始めて、エラー解析が壊れたりする、といった具合だった
運用規模のため、顧客のごく一部だけが影響を受けても数百件の問題報告が入ってくるので、すべての障害が大事だった
今は小さなスタートアップで働いているが、まったく違う。関心と人材が集まる側なので静的言語を選んだものの、動的言語で働いていた頃が懐かしい
速くリリースしていると、コード品質は本当に悪くなる。時間がないため、型システムと向き合ってきれいにリファクタリングするより、少しずつ違う同じようなオブジェクト構造をいくつもコピーして、あちこちで使う
型の形のせいで一般化が難しく、機能を分離して複数箇所から参照するより、同じロジックを 10 か所にコピー&ペーストしてしまう
問題領域によって適切な解法は違うと思う。微妙なバグのせいで真夜中に呼び出されるなら、厳格な静的型が合っている可能性が高い
速く反復でき、問題がメールや Slack 1 通で解決できるなら、動的型のほうがよい場合もある
コツは、成長して静的パラダイムへ移る必要が出たときに備え、動的パラダイムで負債を積みすぎないことだ
熱心なファンが多い言語は、残念ながら「他の言語のライブラリは使いたくない」という態度に流れやすいように思う
Clojure は断然、自分が最も好きな言語で、自然に感じられる
ただし、ほかの言語から本当に恋しくなるものが 2 つあり、それは理解できるエラーメッセージと型ヒントだ
会社で大きなコードをリファクタリングしなければならず、苦痛だった。どの関数が例外を投げるのか、単に nil を返すのか分かりにくく、主要な関数がベクターを返すのか、シーケンスを返すのか、別のコレクションを返すのかも分かりにくい
spec、malli、typed clojure のようにこれを解決しようとするプロジェクトはあるが、Python や TypeScript の型に近い代替物には感じられなかった
関数に何が入り、何が出てくるのかを教えてくれない言語は、数ファイルを超えるコードベースではほとんど価値がないように感じる
現在のコードがオブジェクトに対してできそうなことを見て型を推論しようとするのは、自ら苦痛を招く行為だ
Clojure に初めて触れるなら、このインタラクティブチュートリアルを確認してみるとよい: https://tryclojure.org
易しい段階から中級、難しい段階へと続く、より優れたインタラクティブ学習セットだ
Clojure は楽しい
Rails は 2010 年代の Web 開発を支配し、大成功したスタートアップの多くがその上に築かれた
エンジニアが「おもちゃで遊んでいる」とあざ笑う人は多いが、今私たちが持っている良いもののかなり多くは、かつてはおもちゃだった。Linux も良い例だ
とても「楽しい」言語の厄介な点は、すべてをその言語で解決できると思い込んでしまうことだ
結局たくさんコードを書くことになるが、後から見ると、コードを書かないことが十分に良い解決策だった可能性もある
[1] https://franz.hamburg/writing/clojure-makes-happy.html