📝記事の趣旨
私自身がClojureに入門してから3年ほどが経ち、動けばいいという状態から、段々Clojureらしい書き方がどんなものか?というのを実践や先輩方のコードから学ばせて頂きました。
"Clojureらしい"コードが書けるようになることのメリットとしては、主に可読性の向上、変更のしやすさ、単体テストの容易さ、バグを防ぎやすくなること、などが挙げられるかと思います。
🌟Clojureらしいコードとは?
Clojureの作者、Rich Hickey氏の有名なプレゼンに、Simple Made Easyというものがあり、このフレーズはClojureコミュニティの中で広く共感されているものと思います。
Rich Hickey氏はこのプレゼンで、「単一の役割」「単一の責務」「単一の概念」「単一の次元」のような要素をSimpleと呼んでいます。
この文脈でのsimpleとは「物事が互いに独立していて絡み合っていない状態」を指し、「要素の数が少ない」とか「使いやすい」という意味ではありません。
また、simpleとは逆の、「物事が互いに絡み合っている」ものはcomplectと呼ばれます。
私の解釈では、Clojureらしいコードの書き方とは、コードを可能な限りSimpleな要素で構成すること、すなわち、基本的な構造の値を単一の役割を持つ関数によるフローで加工することである、と考えています。
🔒基本的にimmutableな値を使う
immutableな値とは、変数が宣言されてから値を変更することが禁止されている変数です。
他の言語では、プログラミング初心者が度々戸惑う概念としてx = x + 1
のようなコードがありますが、Clojureの世界、もっと言えば関数型プログラミングの世界ではこのような式は異端です。
要は、x = x + 1
は既に存在する変数x
に対して、x + 1
を再代入する式ですが、Clojureでは基本的に一度定義した変数の値(数値に留まらず、マップや文字列などの全てのデータ型)を変更することは行いません。
基本的には、x
という値に対してx + 1
という値を使いたい時は、(+ x 1)
のようにx
に関数を適用した返り値を使うか、(def new-x (+ x 1))
のように新たな変数定義をすることになります。
この変数の変更の制限は、一見すると不便にも見えますが、このようにすることで、プログラム実行のどの時点で参照しても値が一貫しているという重要なメリットがあるのです。
📦極力基本的なデータ構造を使用する
これはオリジナルなクラスやインスタンスを使ったプログラミングをするのではなく、マップ、リスト、関数のような基本的なClojureのデータ構造で構成されたデータを使用するということです。基本的なデータ構造を使うと、コードリーディングの際に処理がわかりやすくなります。
データの中身をREPL確認しやすい、Clojureやライブラリの関数を簡単に利用できる、といったメリットもあります。
✂️デストラクチャリングを効果的に活用する
Clojureには充実したデストラクチャリングのための構文があります。
特に、map構造に対するデストラクチャリングを使うことで、データを扱いやすい形に分解することと、可読性を大きく上げることが同時に実現されます。
以下は単純なコードをmap構造のデストラクチャリングを使って書き換えた例ですが、これだけでもこの記法による変数利用の利便性向上を感じて頂けるかと思います。
(def alice {:first-name "Alice"
:last-name "Smith"
:age 20})
;; デストラクチャリングを使わない場合
(defn format-user [user]
(str (:first-name user) " " (:last-name user)
" (" (:age user) "歳)"))
;; map構造のデストラクチャリングを使う場合
(defn format-user [{:keys [first-name last-name age]}]
(str first-name " " last-name " (" age "歳)"))
この他にも、有用なデストラクチャリングの記法があるので、初心者は公式のガイド(Destructuring in Clojure)を一読してみることをおすすめします!
🎯アローマクロを使う
Clojureでは入力の値に対して、パイプラインのように順番に関数を適用するフローを実行することで、目的の出力を得る、というスタイルでコーディングする機会が多いです。このようなプログラミングのスタイルに対して、非常に相性が良く、可読性を向上させてくれるのが、アローマクロを使った書き方です。
以下はアローマクロを使った書き換えの簡単な例です。アローマクロを使わない場合には、1つの関数の処理の範囲を一目で認識するのが難しいですが、アローマクロを使って書けば、データに対する一連の操作が1行ずつ上から順に適用される様子が分かりやすくなります。
;; アローマクロを使わない場合
(update (assoc {:name "Alice" :age 20} :email "alice@foo.com") :age inc)
;; アローマクロ(->)を使う場合
(-> {:name "Alice" :age 20}
(assoc :email "alice@example.com")
(update :age inc))
このサンプルコードに使用した->
以外にも->>
, as->
, some->
cond->
などが、引数の位置の違いや、処理条件の違いなどで使い分けられますので、使いこなせるようになると良いでしょう!このトピックにも、公式のガイド(Threading Macros Guide)があります。
🎓おわりに
Clojureは上手く書けるようになるのが中々難しく、上達に時間の必要な言語だと思います。しかし、他の手続き型の言語しか触れて来なかったプログラマに様々な新しい視点を与えてくれる言語だとも感じます。
私自身は例えば、以下のような変化があったように思います。
- データとその操作を分離して考えること
- 入力値から出力値へのパイプライン状に値を変換する、という視点
- 副作用の影響への強い意識
- 問題解決を、極力シンプルで小さな関数の組み合わせで実現できないか考えるような意識付け(これはまさに、Simple Made Easyの考え方です)
これからClojueを学ぶ方々が、新たな発見や学びが得られるよう願っています!