2021年1月から Clojure を使って仕事をしている。長年主にRubyを使った受託開発をしてきたのだが、今年はかなりClojureメインで仕事をすることができた。
Ruby から Clojure へ
Ruby にあまり不満はなく(Railsには多々あるが)今でも十分に好きな言語なのだが、数年前から趣味で触り始めた Clojure が存外によくてもっとこの言語を触っていたいという気持ちが自分の中で高まっていた。 Clojure は Lisp とか JVM 上で動くとか色々特徴のある言語だが、僕としてはそういうのは Clojure を構成する要素の一つでしかなくて、もっと重要なことは Clojure の世界観とか Rich Hickey の設計に対する考え方とかで、僕としてはそういう部分に魅力を感じている。 この辺の話は t_yano さんのブログにわかりやすくまとまっているので読んでみてほしい。
Ruby と Clojure は色々と似ている部分もあって、例えば Hashmap をうまく使おうとするところや map/reduce のサポートがしっかりしているところなどがある。根底にあるものが Ruby はオブジェクト指向であり Clojure は関数型という違いはあるものの両者はよく似ている(と僕は思っている)。
僕が Rails や Hanami でやっていたサービス層をつくってドメインロジックをまとめる設計はコマンドパターンでクラスをつくる方法をとっていたがこれはそれなりに冗長なやり方でもあって、 Clojure だとただ関数を定義するだけでほとんど同じことができた。 仕事でも趣味でも主に duct (または integrant ) を使っているのだが duct はとても疎結合にシステムを構築することができる。 duct の本質は設定用の edn ファイル (他言語でいうjsonみたいなもの) で各関数をどのように結合するかを定義することにある。関数がどの関数を呼び出すかという非常に密結合になりやすい部分が duct (integrant) を使うと疎結合にできる。しかもそれを実現する方法は multimethod という clojure の機能をうまく使っていて duct (integrant) は非常にコンパクトに作られている。 Rails や Hanami のようなフルスタックのフレームワークと duct を比較するのも無理があるが duct のシンプルさには驚いた。
Clojureを仕事にする
フリーランス(今は法人化しているので社長だが)として仕事をするメリットはやりたいことを選べることじゃないだろうか。 自分一人分の売上をあげるだけなら案外なんとかなる。会社にいると会社の方針や人間関係により選択できる技術や仕事が限られてくるが、フリーランスだとある意味どんな仕事でも受けることができる。今までRubyの案件が多かったが、試しにClojureの仕事を探してみることにした。
Clojure は日本で採用している会社がすくないが何社か営業してみたところ運良く1社から良い返事をいただくことができて2021年1月から契約を始めた。 また duct (integrant) を採用していたので事前知識を活かすことができた。
仕事で Clojure を書いてみて思ったこととしては
- 楽しい (一番大事)
- コードレビューで他の人のコードが読めるようになるまで少し時間かかった
- コードを書くのは最初からあまり問題なかった
コードを書くとき僕はまず既存の設計を理解してそれに沿うように書くことにしている。 そのためまず既存のコードを読まなければならない。これは duct (integrant) という枠組みのおかげもあり理解が早かった。 duct (integrant) の場合、 config.edn を読めばどの関数がどの関数に依存しているかわかる。
次にコードを書くステップになるわけだけど、これも自分の考えを表現する作業なので楽しい。 Clojureの場合ドキュメントがしっかりしているので ClojureDocs を見ればコードが書ける。
コードレビューはそれまでの前後関係を理解してないと読めないので、読めるようになるまで少し時間がかかった。 Clojureの特徴としてコードが短くなるというのもあって、「この reduce の処理何やってるんだ」みたいなことを理解するのにはじめは時間かかった。 Clojureの場合前提としているデータ(だいたいHashmap)がどのような構造をしているかが分かりづらいことがあって、 コツとしてはそこをきちんと理解していれば読みやすくなる。 このへん、やはり Clojure Spec などで明記するようにしたほうがコードは読みやすくなる気がする。
改めて感じた Clojure のよさ
大量のカッコを伸ばしたり縮めたり、ゴムのような言語だなと感じている。 if とか for とかも全部関数なので必ず戻り値があるからどこにでも差し込めるのが良い。
threading macro (->とか->>とか) を使えばデバッグ処理を差し込みやすいのも好き。 例えば
(->> data (map (fn [d] (...))) (filter (fn [d] (...))))
こんな感じの処理があったときに
(->> data (map (fn [d] (...))) ((fn [a] (prn a) a)) (filter (fn [d] (...))))
こんな感じで print デバッグできる。map関数の結果を prn で表示して結果を filter の方に渡す。 処理を邪魔することなくデバッグを追加することができる。こういうのがなんとなく気持ちいい。
やはり関数を並べるだけで処理が作れるのは便利だ。 some->
なんかも好き。
some->
は並べた関数を ->
で実行する関数だけど途中で nil
を返した場合そこで処理が終わる。
これにより正常系ではデータを作っていって、異常系では nil を返して処理を中断するということができる。便利。
Clojureの良さは色々感じるけどやっぱり上記のような関数として処理を構築するという部分が一番楽なきがする。 たまに手続き書きたくなることがあるけど案外全部宣言的にかけるものだなとも思った。