tree roots on rock formation

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クラスタには特に向いていると思う。根本的なところからライブラリを整備するところはボーイスカウトな気分にもなれる新しい面白さがあって楽しみ。