ErlangはEricssonがはるか昔に開発した通信環境用の言語で、ものすごく頑丈で安定している環境として有名。実際に世界中のモバイル通信環境で用いられ99.9999999% (“nine nines”) の可用性を誇っている。最近ではRuby風でErlangのBEAMを実行環境とするElixirが流行っている印象がある。分散を前提にしている関数型言語としてClojureに近いと感じた。そして実際にBEAMの上でClojureを実装しているものがある。
Clojureが最初はJVMと.NETのCLRも対象にしていたが後JVMだけになり、またClojureScriptの登場でJavaScript上で動くようになった。だから根本的なところは案外実行環境に依存しないところもあるかもしれない。BEAM上でClojureを実装しているClojerlはあくまでもコミュニティーからのもので正式なClojure版ではないが、試したかったBEAMと選べるなら選ぶぐらい好きなClojureが合ったものに当たるから触ってみざるをえなかった。
辛かったところ
Clojerlみたいなプロジェクトでありがちな課題はドキュメント。Clojerlも実際コード外のドキュメンテーションはほとんど皆無でClojureScriptの紹介文を模写したものぐらいしかない。
またハローワールドのウェブサービスの例文もDockerのclojerlイメージがlatestしかないところもあって動かなくて、作られてから2年間更新されてないから使うライブラリーも古くなったりしてるから動かすまで簡単には行かなかった。(現状は諸事情によりPRかけられないが年明けてから更新するPR投げようと思っている。)
つまり円滑な開発体験を期待して行くほどまだ熟成してはいないものと考えてもらえると妥当。動くのは動くし、大体は期待した通りにも動くが、まだまだ改善の余地が多々残っている。
触ってみたところ
ずっとウェブ(API)開発をメインにしている者として最初に気になるのはフィボナッチ数列の素早い算出よりも、ピカイチのパフォーマンスが発揮できるHTTPサーバーである。ElixirのRails代替ライブラリであるPhoenixがあるからHTTPには困らないだろうと、そこで使われるCowboyというライブラリ(マスコットキャラが激かわ)で試したところDocker内でもそこそこの力を発揮してくれた。
$ time h2load -n 100000 -N 1 -c 16 http://localhost:8080 starting benchmark… spawning thread #0: 16 total client(s). 100000 total requests Application protocol: h2c finished in 2.30s, 43569.78 req/s, 1.41MB/s requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx traffic: 3.24MB (3401904) total, 392.20KB (401616) headers (space savings 94.34%), 1.14MB (1200000) data min max mean sd +/- sd time for request: 89us 14.39ms 360us 224us 94.12% time for connect: 540us 2.97ms 1.74ms 725us 62.50% time to 1st byte: 3.98ms 13.24ms 7.82ms 2.29ms 81.25% req/s : 2725.26 2774.78 2746.09 14.10 62.50% real 0m2.323s user 0m0.631s sys 0m1.092s
もちろんまだnettyを使って最適化されたものとはまだ比べたくないが、調整して磨けば十分の可能性はあるように見た。Cowboyの特徴として接続にBEAMのプロセス(OSのプロセスではなく)割り振り、入ってくるリクエスト毎にまたプロセスを分ける方針。そのためどれかのリクエストの処理で変なエラー起こして死亡したとしてもそのプロセスだけ死んで他は無事で済む。
本家のDockerイメージがlatestしかなく中身が何になっているのか不透明なため、親イメージの erlang:22.2.3
を元にrebar3
(印象としてはErlang界のmaven)を使って試した。Clojerlはrebar用のプラグインは提供してくれているのでビルドは容易。
Clojerlのコードの中へ
Clojerlは基本的にClojureの文法を引き継いでいて関数名も一緒。だから通常使う様なclojure.coreの関数(assoc
とその仲間たち)は期待するように動くだろう。reduce
などの高階関数も基本的には動くが、reducerに関しては未解決なイシューはまだある… が普段の使い方で関係あるかって言われると、ない。
Clojureの高階関数の極みにあるtransducerも使えるよう。個人的にtransducerは未だに使いこなせていないところもあって、細かくは試してないがドキュメントの例文のものはClojerlでも問題なく動いた。
clje.user=> (let [xf (comp (filter odd?) (map inc))]
(transduce xf + (range 5)))
6
制限として一つ見つかったのは関数を def
で定義できない模様(”Init value is not a literal”と言われる)。これはRingのハンドラをマクロで生成するところなら考えないといけないところ。
Erlangライブラリ(今回はCowboy)との連携はめっちゃスムーズ。まだ :import
の使い方は見つけられてはいないがあってはありそう。Erlangで書くように cowboy_router/compile
を呼び出す場合は別に import
せずにも動く。Erlang連携で言うと一つちょっとおもしろかったのはErlangとClojureで諸々のカッコの意味が違ってて、Erlangの関数を呼ぶ時に”これは何だっけ”と一瞬混乱する。
Erlang Clojerl
tuple {} #erl[] list [] #erl() map #{} #erl{}Erlang Clojerl
{} tuple map [] list vector #{} map set
後味
仕事で使おう!とならないとしても、Clojerlは十分に魅力的と感じた。現状は例えライブラリが少ないかも知れないが、Clojureで成功しているパターンやプラクティスは見えているので悩み少なく必要なものは実装できそう。またErlangの実行環境はJavaほど数がないがJavaよりも頑丈性とか意識したライブラリは多数あって、Elixirの成功もあってこれからまた増えるだろうと期待できる。
プロジェクトとかサイト動かすに使えるように個人的に貢献しようと思っている。BEAMはJVMと比べて圧倒的に軽いというところもあってこのブログも動く軽量k3sクラスタには特に向いていると思う。根本的なところからライブラリを整備するところはボーイスカウトな気分にもなれる新しい面白さがあって楽しみ。