Clojureの思い出

ふと思い立ったので、記憶から忘却される前に書いてみようと思います。もう2年くらい書いてないけど、4年ほど仕事で使っていました。その期間はコード書くときはほとんどClojureだったので、まあまあヘビーに使っていたと思います。

前提

メリット/デメリットはコンテキストによるだろうと思うので、どんな感じで使ってたかを書いておきます。

  • 機械学習/自然言語処理の研究開発用途
    • 最近だとPython一択になりつつありますが、数年前はまだそうでもなかった
  • プロダクションに使われるわけではなく、論文を書くための実験や同僚に見てもらうためのデモを時々作ったりする用途
  • 最終成果物はコードではなく論文で評価されるので、メンテナンス性よりも素早くアイディアを実現させること優先
  • チームではなく基本的に一人でコードを書く文化だったので、自分のパフォーマンスがmaxになる言語だったら何でもいい
    • チームのメンバー数 = チームで使われる言語数という感じで、C++、 Python、Java、Matlab、Perl、Ruby、Allegro Common Lispなど統一感は全くない

Clojureを使うようになったきっかけ

  • 大学院時代、機械学習/自然言語処理の研究をやるのにC++で書いていた
    • ベイズを中心にやっていてMCMCなどでパフォーマンスが必要なことが多かった
    • 人類(少なくとも私)にはC++は早かったのじゃ...という気持ちになった
  • 他の言語に乗り換えたい
  • PythonやScalaが選択肢にあったが、普通の言語っぽいし(??)、もっと刺激が欲しいという気持ちが勝って、ClojureやHaskellが選択肢にあった
    • 修論前に現実逃避できるおもちゃが欲しかったのかもしれない...
    • とはいえ、既存の構文解析器もシームレスに使えないと研究に支障が出るということでClojureを選択した
    • 関数型、マクロなど自分がこれまで経験したことがないパラダイムだったので、面白そうだったり、実際面白くて役に立った
    • Haskellの本も読んだけど、Clojureを書くうえでかなり参考になりました

ここがよかった、Clojure

Clojureに限らない話が多いですが、書いていて気持ちよかった点をいくつか。

Immutableな世界

Clojureをやる前はC++をやっていて、思わぬ副作用で苦しんだ時もありました。Clojureはimmutableなデータ構造が前提なので、コードを書くときに考えればよいスコープがすごく狭く済むことが多く、脳内のメモリも少なく済み、テストも書きやすかったです。

リスト系関数の充実

Lisp系の言語なので、リストに対して何らかの処理を数珠つなぎのようにやることが多かったです。その際にthreadingマクロが役に立ちました。

mapの中にあるような関数が充実していて、coreにある関数で大体どうにかなる安心感はよかったです。Linux的(パイプをつないで小さい関数で加工していく)というか、小さい関数をthreadingでつないでいけばいいんだ、というのは他の言語を書くときにも役に立っているなーと思います。小さくimmutable前提の関数をたくさん書いていくので、テストもしやすい作りに自然となっていきました。

困ったときにはJavaに逃げられる

Clojureはパッケージが結構充実してるので、特にデモ環境とか試してもらうためのcli/apiを作るための道具は探すと結構あってそこまで困りませんでした(compojureとか使っていた)。機械学習の最適化などでマニアックなことをしようとすると流石にClojureだけでは厳しくなりますが、JavaのライブラリもClojureの中でそこまで違和感なく使えます。つまりmavenにあればなんとか出来る安心感。英文からの特徴量抽出にStanford ParserBerkeley Parserを使いましたが、両方ともJavaで書かれていてかなり恩恵を受けました。liblinearなどの大抵の分類器もJavaにならあるので、自分に必要なところだけ書けばいい。

また、パフォーマンスで困ったらJavaの配列に逃げらるので、これまた助かりました。JavaっぽいところはClojure likeに使えるようなマクロを用意してラップして使ってました。

マクロ

数年前はまだ人間が特徴量抽出を行なっていた時代だったので(冗談です)、構文木から様々な特徴量を人間がテンプレートとして書いていました。特徴量抽出は似たようなコードが並びがちですが、そういったときにマクロを使って似てるけど微妙に違う関数を自動で大量に生成する、といった用途で使っていました。係り受け解析器での例をいくつか挙げておきます。

乱用は厳禁ですが、便利だし、コード生成は脳汁が出ますね...。チーム開発で乱用されたら殴りたくなるとは思う。

Deployが容易

手元のMacで書いて、計算サーバーで実行というときに環境の差異があるとイライラしますね。今ならDockerやpyenvなどあるので事情が大分違いそうですが、当時はこの辺りをほとんど知りませんでした。Clojureなら成果物をjarファイルにまとめてサーバーにscpすれば動いてくれるので、deploy回りで困ることが少なかったのはよかったです。

並列処理が容易

機械学習をやる上で大量のデータを早く処理したいことは多々あったので、Clojureが並列処理が得意というのには助けられました。(map hoge list)のように書いていた箇所を(pmap hoge list)に変えればいいだけということも多く、とにかくお手軽でした。研究所時代は24コア/メモリが1TBのマシンも普通に使えたので、とにかく富豪的に解決すればいいやろという環境であったことも書き添えておきます。

ClojureScript面白い

研究やっているとjsを書く機会はほぼ皆無ですが、デモ環境を作っているときにjsっぽいことが書けるとUIがちょっとリッチになります。とはいえ、デモ環境のためだけにjsの学習コストを払いたくない(それをやるなら論文もう一本書きたい)...というときにClojureScriptを見かけました。いわゆるAltJsでフロントエンドまで含めてClojureで完結するのは結構面白かったです。サーバーサイドの関数を使い回せたりするのは用途よってはよいかもしれません。

ここが辛いよ、Clojure

書いていた当時に辛かったこと、今チーム開発でClojureを書くとしたら辛いだろうなと思うことが混在しています。

コード上で型が分からない

最近はScalaやGolangを書くことが多いですが、変数や引数に型があることでレビュー/コードリーディングでとても助かっています。Typed Clojureはありましたが、ディファクトになりきれていないように見えました。最近だとcoreに入っているようなので、事情が違うのかもしれません。

とはいえ、型の記述が必須だと当時はプロトタイプ書くのに邪魔だと思って選ばなかったかなと思うので、難しいところですね。

パフォーマンスチューニング

パフォーマンスでつらくなったらJavaに逃げられるとは言え、なるべくJavaは書かずにClojureで済ませたいものです。ボトルネックの箇所のみJavaで書くことにしたいですが、Clojureでのパフォーマンスチューニングは結構難しかったです。プロファイラをかけて、どの関数が重くなっているか目星を付けたいときにコンパイル元のどのClojureの関数に対応しているかがよく分からないことがあり難儀した記憶があります。マイナーな言語なので、パフォーマンスチューニングの事例が調べてもあまり出てこなかったというのもあるかもしれません。

ちょっとしたcliアプリが書きにくい

起動してしばらく使うようなアプリはよいのですが、jqのようにちょっとしたcliアプリを書きたくなることはよくありますが、Clojureは起動に時間がかかるためこういった用途では積極的には採用しにくかったです。この用途は今だったら完全にGolangで書いてしまうなぁ。

再帰をよく使う

慣れれば特に難しくはないけれど、慣れるまではちょっと困る人も多そう(再帰で書ききれないので、transientで逃げるとか)。チーム開発をやる場合には他の言語と比べると導入が大変だったり、慣れている人が少ないといったことがあるかもしれません。自分の場合はThe Little Schemerを読んで練習したりしていました。

プログラミングClojure 第2版

プログラミングClojure 第2版