なぜClojureなのか?
(gaiwan.co)- Clojureは主流のプログラミング言語の一つではなく、一部の人にはなじみが薄いかもしれない
- Clojureの主な利点
- 開発者の生産性 : Clojureはインタラクティブで、反復作業が少なく、効率的な開発環境を提供する。開発者が満足しながら素早く製品をリリースできる
- 長期的な保守性 : Clojure言語とそのエコシステムは成熟しており安定している。高品質なシステムを構築しつつ、保守コストを削減できる
- アイデア中心の文化 : Clojureコミュニティは過去と現在、学術界と産業界のアイデアを探究し、より良いソフトウェア開発の方法を模索している。開発者に新たな挑戦と学習の機会を提供する
(hello 'clojure)
- Clojureは1950年代に生まれたLisp系言語の一つ
- Lispは理論モデルとして始まったが、実際のプログラミングにおいても高い概念的な優雅さを提供する
- コードの構文がそのままデータ構造と一致するため、従来の文法が複雑な言語よりも多くの利点がある
- 過去のAIブームの際には主要な言語として使われていた
- Lisp系言語は周期的に人気を得ては消えてきたが、ここ10年ほどはClojureが注目を集めている
- ClojureはJavaのJVM上で動作し、最新のプログラミング概念を反映している
- 強力な不変データ構造のサポート
- 並行性を考慮した設計
- 大手企業の強力な支援なしに成長してきた小さなコミュニティだが、強い言語的特性を備えている
- Clojureはさまざまな環境で実行できる
- ClojureScript: JavaScriptに変換して実行
- ClojureCLR: .NET環境で実行
- Babashka: GraalVMを活用した高速なスクリプティングインタプリタ
- Jank: ネイティブコンパイルをサポート
- Clojureを学べば、複数の環境で同じ知識を活用できる利点がある
インタラクティブな開発方式
- プログラミングはコードの記述と検証を繰り返す過程
- フィードバックがなければ、コードが正しく動作するか確信するのは難しい
- 一般的なフィードバックの方法:
- スクリプト実行の反復
- UI操作とログ出力
- 単体テストの活用
- コンパイラや静的解析ツールの使用
- 速いフィードバックは開発者の生産性に大きく影響する
- フィードバックが遅くなるほどデバッグ時間は増え、コードを書く時間は減る
- Clojureは従来のフィードバック方法に加えて、インタラクティブ開発(interactive development) という方式を提供する
- REPL (Read-Eval-Print Loop) ベースの開発が中核
- 開発者はコードを書く前にClojureランタイムを起動し、エディタと接続する
- コード片を一つずつ実行しながら即時のフィードバックを得られる
- Lispの一貫した構文構造のおかげで、小さなコード片から大きなコードブロックまで自由に実行できる
- 一般的なREPLと異なり、Clojureでは実行中のシステムに接続した状態でコード片を実行できる
- この方式は従来の「コードを書く → 実行 → デバッグ」方式よりはるかに強力なフィードバックループを提供する
- リアルタイムでプログラムを修正しデバッグできる
- 単なるREPLではなく、運用環境でも活用できる強力な対話ツールである
安定性を重視する文化
- Clojureを選ぶことは、単に強力な技術を得るだけでなく、独特の哲学と原則を持つコミュニティの一員になることでもある
- 技術だけを採用してコミュニティと交流しないなら、Clojureの本当の価値を体験するのは難しい
- Clojureコミュニティは安定性と後方互換性を重要な価値とみなしている
- コア言語はほとんど絶対に破壊的変更を入れないまま継続的に改善・拡張されている
- Clojureのオープンソースエコシステムも同じ哲学に従い、不要な変化(churn)を最小限に抑える
- これは他の多くの現代的なプログラミング言語エコシステムとは対照的である
- 不要な変化によるリソース浪費は、世界全体で数十億ドル規模に達する
- Clojureでは、言語やライブラリを最新バージョンにアップグレードすることが非常に自然である
- バグ修正、セキュリティ更新、性能改善の恩恵を受けつつ、コードベースを書き直す必要がない
- 他の言語では新バージョンの登場で壊れうる既存コードが、Clojureではそのまま維持される
- 一部の開発者は「変化なしにどうやって発展できるのか?」と疑問を持つかもしれない
- しかし、安定性と停滞(stagnation)は異なる
- Clojureは既存機能を壊さずに新機能を追加し改善する形で発展している
- 開発者は不要なコード修正なしで、継続的により良いツールを使える
情報システムと知識表現
- Webおよびビジネスアプリケーション開発では、情報の収集・アクセス・処理が中核となる
- しかし主流のプログラミング言語は、情報の表現と操作の面で非効率な設計を示すことが多い
- 低水準データ構造を強いることで不要な複雑さを招く
- 静的型システムが過度に硬直的で、柔軟なデータ操作が難しい
- Clojureは関数型データ構造と強力なデータ操作機能を標準で提供する
- 動的型付き言語として**「開かれた世界仮定(Open World Assumption)」**に従う
- これはデータの拡張性と柔軟性を最大化する考え方である
- RDF(Semantic Webのためのデータモデリングフレームワーク) の影響を強く受けている
- 代表例としてDatomicのようなグラフデータベースとの相乗効果が大きい
- 名前空間付きキーワードを活用することで、文脈に依存しない意味付けが可能になる
- 動的型付き言語として**「開かれた世界仮定(Open World Assumption)」**に従う
- 名前空間キーワードを活用したClojureマップ(Map) 構造は、単純なJSONよりも精緻な意味表現が可能である
- 追加のデータ拡張が容易で、名前衝突を避けつつ直感的なデータ表現が可能である
小さく組み合わせ可能な関数と不変データ
- Clojureでは純粋関数(pure functions)と不変データ(immutable data) を中心にプログラミングするのが一般的である
- Java、Ruby、Cスタイルの命令型コードも書けるが、Clojureらしいコード(idiomatic Clojure) は大きく異なる
- 純粋関数: 入力値だけに基づいて結果を返し、外部状態を変更しない
- 不変データ: 参照(reference)やオブジェクト(identity)ではなく、値(value)ベースの意味を持つ
- 外部状態の影響を受けないため、コードの予測可能性が高い
- グローバル変数の変更や予期しない副作用(side effects)がない
- データが変更される危険がないため、並列処理や並行性の問題を解決しやすい
並行処理
- 現代のコンピューティングはマルチコアプロセッサを前提としており、並行性は必須の要素である
- Mooreの法則が限界に達するにつれ、並列性の活用が性能向上の鍵になっている
- しかし可変状態(mutable state) を扱うと、同期の問題や複雑なタイミング制御が必要になる
- Clojureは不変性(immutability) を強調することで、並行性の問題を根本から解決する
- 可変メモリを操作すると、タイミングや順序への依存が生じる
- 純粋なデータ変換(data-in, data-out)方式は常に安全に並列実行できる
- ClojureはJVMの既存の並行性機能(
java.util.concurrent)を活用しつつ、より高水準の抽象化ツールを提供する- Atoms: CAS(Compare-and-Set)と自動リトライを活用した原子的操作をサポート
- Refs: ソフトウェアトランザクショナルメモリ(Software Transactional Memory, STM)を提供
- Agents: 非同期で更新を適用する方式
- Futures: スレッドプールベースのfork-and-joinインターフェースを提供
- core.async ライブラリを提供
- Goのgoroutineに似たCSP(Communicating Sequential Processes) パターンをサポート
- Erlang/ElixirのActorモデル、ScalaのAkkaと比較できる
- Clojureの高水準抽象化機能を使わず、より低水準の並行制御手法を活用することも可能
- 同期キュー、原子的参照、ロック(lock)、セマフォ(semaphore)、さまざまなスレッドプール、手動スレッド管理などをサポート
- 必要に応じて細かな並行制御も可能だが、ほとんどの場合は抽象化されたツールを活用する方がより安全で効率的である
ローカルリーズニング(Local Reasoning)
- 一度に考えられるコードの複雑さには限界がある
- プログラムの状態や変更があちこちで発生すると、コードの理解と保守が難しくなる
- Clojureがローカルリーズニングを容易にする理由
- 純粋関数(pure function)中心のコード
- 入力値だけで関数の動作を完全に理解できる
- 関数外部の状態を考慮する必要がない
- オブジェクト指向言語との違い
- Clojureは多相性(polymorphism)を最小限に抑え、コードがどこで実行されるのかを把握しやすい
- オブジェクト指向プログラミング(OOP)では「すべてが別の場所で起こる」が、
Clojureでは名前空間に定義された関数だけを追えばよい
- 純粋関数(pure function)中心のコード
- Clojureの一貫した構文構造のおかげでコードのリファクタリングは容易である
- 不変データと純粋関数の使用により、コード変更時の予期しない副作用(side effect)が少ない
- 最小限の命令型コードだけを別途分離し、命令型コードと関数型コードを調和的に構成できる
テストのしやすさ
- Clojureの純粋関数ベースのコードでは、入力値を与えて出力値を確認するだけでテストできる
- テストのための複雑な状態初期化、モック(mock)オブジェクト設定、タイミング制御などが不要
- そのためテストの信頼性が高く、「フレイキー(flakiness, 一貫しないテスト失敗)」が少ない
- 高度なテスト手法であるProperty Based Testing(Generative Testing) をサポート
- ランダムな入力値を生成し、特定の性質や不変条件に違反するケースを探索する
- 最小の失敗事例を見つけるshrinking手法をサポート
- この概念はHaskellで始まり、さまざまな言語でQuickCheckベースのテストフレームワークとして実装されている
- Clojureの不変データ構造とREPLベースの開発方式と相乗効果を生み、さらに効果的である
Clojure開発者採用の利点
- 一般にJavaScriptやPythonなどの大衆的な言語に比べ、Clojure開発者は相対的に少ない
- しかしClojure開発者が少ないのと同じく、Clojureを使う企業も多くはない
- そのため全体として需給のバランスは保たれている
- 実際にClojure開発者を求める企業と開発者は、互いに適切にマッチすることが多い
- Clojure開発者は高い問題解決能力を持つ場合が多い
- 単に人気の技術を学ぶのではなく、新しい考え方やアイデアを探究する開発者が多い
- 実際にClojureを使う企業は、応募者数は少なくても質の高い開発者が多いと評価している
- 例: Nubankはブラジルで数百人の開発者をClojureで直接教育し、成功事例を残した
- Clojure開発者を見つけるのは難しいが、適切な人材を見つけられれば優れた開発者を確保できる可能性が高い
- 単にその言語経験のある開発者を探すより、学習能力の高い開発者を育成することも良い選択である
- Clojureコミュニティ自体が問題を深く考え、解決する開発者を引きつける特性を持つ
トレードオフと抽象化レベルの調整
- Clojureは高水準(high-level)言語であり、簡潔で表現力の高いコード記述を志向する
- 関数型(不変)データ構造と強力なデータ操作APIのおかげで、不要な複雑さが減る
- Clojureのデータ構造は不変性保証のため内部的に木構造(Hash Array Mapped Trie)を使用している
- 更新時にはパスコピー(path copying)が発生し、GC(ガベージコレクション)負荷が増える可能性がある
- Javaとの相互運用(interop)の過程でランタイムリフレクション(reflection)、ボクシング/アンボクシングが発生することもある
- 一般的なアプリケーションではこのコストはほぼ無視できる水準であり、開発生産性向上の利点が大きい
- リアルタイムグラフィックスエンジン、信号処理、数値計算など高性能が必要な場合は低水準最適化が可能
- Clojureではタイプヒント(type hint)を提供することでリフレクション除去とプリミティブ型演算の最適化が可能
- 連続したプリミティブ型配列の使用でCPUキャッシュを活用できる
- GPUアクセラレーションライブラリも利用可能
- 多くの高水準言語は、性能が重要な場合にC/Rustなどのネイティブ拡張を使う必要があるが、
ClojureはJVMの最適化(JITコンパイル)を活用して大半の性能問題を解決できる- プロファイリングと多少の最適化により、イベントループなどの性能を最大化できる
メタプログラミングとデータ中心API
- ClojureはLisp系言語としてコードをデータのように扱える
- JSONに似ているが、より読みやすい構造でプログラムを表現できる
- EDN(Extensible Data Notation) というデータフォーマットを活用し、JSONに近いデータ表現を提供する
- Clojureはマクロを使ってコード自体を変換する機能を提供する
- しかしClojureコミュニティにはマクロ使用を慎重に抑制する文化がある
- マクロはデバッグが難しく、静的解析ツールとの互換性が低くなることがある
- その代わり、データ中心(data-driven)のAPI設計が好まれる
- 例: HTTPルーティング、HTML/CSS生成、データ検証など
- 特定の関数を直接呼ぶ代わりに、データ構造(マップ、ベクタ)を使って動作を指定する
- データベースAPIは動的に操作でき、ユーザー設定を容易に保存・修正できる
- データ中心APIのおかげで、ランタイムでシステムを動的に再構成できる
- これにより高度に柔軟なシミュレーションシステム、動的設定管理、メタプログラミングを容易に実装できる
Java相互運用(Interop)とエコシステム活用
- 現代のアプリケーション開発は膨大な数のオープンソースライブラリやAPIを組み合わせる過程である
- ClojureはJVM上で動作するため、Maven Centralにある数百万のJavaパッケージを活用できる
- リフレクションなしでも簡単にJavaライブラリを呼び出せる
- ClojureはJavaよりコードがはるかに簡潔で、REPLを使った実験的プログラミングがしやすい
- Javaよりずっと速くAPIを探索し組み合わせられる
- ClojureScriptを使えば、同じ方法でJavaScriptとNPMエコシステムを活用できる
アイデア中心の文化
- どの言語を使っても成功するプロジェクトは可能であり、逆に誤ったやり方で使えばどの言語でも失敗しうる
- 良い道具が良い開発者を作るわけではない
- Clojureは入門難易度が高く、学習過程で試行錯誤が必要だが、それを通じて深い思考が可能である
- 一部のClojureプロジェクトではJavaやPythonスタイルの書き方が残っており、十分に活かされていない
- しかしClojureの哲学とアイデアを受け入れたチームは、より良いソフトウェア設計能力を身につける
- Clojureコミュニティは既存の開発手法に絶えず疑問を投げかけ、より良い方法を探究している
- Rich Hickey(クロージャ創始者)の講演は単なる技術紹介ではなく、根本的なソフトウェア設計原則を探究している
- Clojureカンファレンスではライブラリ紹介よりもアイデア、論文の分析、経験共有が中心である
結論
- Clojureは単なるプログラミング言語ではなく、より良いソフトウェア開発の方法を考える人々のコミュニティである
- このコミュニティでは自分の限界を広げ、新たな可能性を探ることが中核的な価値である
- この文化の中で成長する開発者は、単にClojureをうまく扱うだけでなく、より優れた問題解決能力を備えたソフトウェアエンジニアになる
3件のコメント
個人的に Clojure を使っていますが、本文の内容にはとても共感します。
業務では主に Python と Java(Type)Script を使ってきましたが、少しでも管理を怠ると、言語自体やライブラリの変化に追いつけず、すぐにレガシーコードになりがちでした。一方で、Clojure は一度書いておいたコードを 1 年後に見ても、すぐに修正・開発しやすい点に非常に満足しています。
その後は個人用途では、特定のライブラリの制約がある場合を除いて、Clojure を愛用しています。
なぜ Clojure なのか?
Jank Jank~!
Hacker Newsの意見
どんなプログラミングを楽しんできたかと聞かれたら、シェルでデータを処理するパイプラインを構築することと、この5年間 Clojure と ClojureScript を書いてきたことだと思う
Clojure を12年間使っており、その前は12年以上 Java を使っていた
Clojure を書くことが大好きで、他の言語と比べても Clojure に対する深い愛情を説明する必要はないと気づいた
共同創業者は、最小限の会社で最大限の製品を作ることを目標にしている
Clojure を使って10年間 SaaS ビジネスを運営しており、Clojure なしでは不可能だっただろう
Clojure を使う人には <a href="https://www.flow-storm.org/">Flow Storm</a> を勧める
Rich Hickey から多くを学び、Clojure と FP に対する情熱があった
ClojureDocs のドキュメントが古いという指摘があり、回答に投票できる機能を追加したいと思っていた
Clojure の安定性に関する部分に驚き、毎年試すたびにすべてが変わっているように感じた
Common Lisp で始めた後、Go と Rust に移ったが、最近また Clojure を見直している