Elixirについて調べたついでに、BrainF**kインタプリタを書いてみた
最近、某D社の事例を含めて、Elixirの話題をよく見かけるようになりまして
もちろん、話題になっているからには、
何らかの「時代の要請」があるはずでして、そのあたりを調べてみたわけです
なぜ、Elixirが出てきたのか?
まず読んでみた本はこちらです(´・ω・)っ
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
この本は言語の使い方について書いた本ではなく、
「設計思想」について書かれた本でして、
そのこと自体がElixirの出自を象徴しております
歴史の流れを復習してみると、かつてWebはPerlやJavaがメインで、
普通のWebであればだいたいPerlのCGIで書かれていたわけです
でも、ある程度大きな機能になるとPerlではきつくなってきて、
「もっとちゃんとした言語」が求められ、
RailsをきっかけにしてRubyがブレイクしました
Rubyのポイントは、まつもとさん自身が書いているように・・・
- 作者: まつもとゆきひろ
- 出版社/メーカー: 日経BP社
- 発売日: 2016/12/22
- メディア: 単行本
- この商品を含むブログ (2件) を見る
・・・多くの言語が「機能」からの要請で生まれてきたのに対し、
Rubyは「コードをこう書きたい」とか「こう書けると気持ちいい」といった、
「設計思想」を優先して生まれてきた、という経緯があります
その分、(特に1.9より前においては)実行速度に難があったのですが、
システムの大規模化・複雑化や、CPU速度の飛躍的向上により、
メリットがデメリットを大幅に上回るようになったわけです(`・ω・´)
一方で、時代はPCからモバイルへ変わっていき、
「大きなリクエストを素早く」ではなく、
「小さくて大量のリクエストを素早く」という要求に変わっていきました
古いアーキテクチャではこの要請に応えられない*1ため、
Nginxが登場したり、Apacheのアーキテクチャが変わり、
さらなる高速化のためWebSoketやそれに対応するNode.jsが出てきたりしたわけです
そして、CPUのアーキテクチャも変化しました
単純な速度ではもはや向上が見込めないため、
マルチコアにするのが当たり前になってきています
その結果、「小さくて軽い処理(をするAPIサーバ)」が求められるようになり、
Rubyではこれらの状況に対応できない・・・ということはないのですが、
Railsもスリム化できるようになり、Node.jsと組み合わせたりするようになりました
(勘違いしないでもらいたいのは、Rubyがダメということではありません
Rubyの強みは「時代の要請についていくセンス」と、
「変化を許容し、時に古いものを切り捨てる強さ」にあるので)
一方で、「小さく軽い処理のを組み合わせて一つの大きな処理を構成する」方式は、
まさに関数型言語が得意とする領域です
しかし、関数型言語は難しすぎます
古くはLisp・Haskellがありますが、これを「普通の人」が扱うのはとても無理*2です(´-ω-)
その領域をScalaが埋めていくのかな・・・と思っていたのですが、
(少なくとも私は)某D社以外に大きな事例も聞かず、
「Play Framework」のWebにおける大きな採用事例も(少なくとも私は)知りません
そもそも、当の某D社がScalaを切った・・・とまではいかずとも、
「Scalaで書き直しました!」と大々的にアピールしていたシステムを、
すでにElixirで書き直しているあたりで、だいだい状況は推測できます(´・ω・`)
他の関数型言語だと「F#」あたりなのですが・・・
あれは本を読んでみたものの、難易度的にはScalaと大差はなく、
(少なくとも現状では)Windowsっぽいイメージが強すぎてきついです
難易度を無視すれば、「小さなロジックを大量のプロセスで処理する」*3という、
理想的な設計で事例もある「Erlang」も使えるはずなのですが、
パラダイムのジャンプだけでなく、文法的な難しさがつきまといます
こんな状況の中、「関数型言語をもっとなじみがある文法で」、
より端的にいえば「Rubyが書きやすいんだから、Rubyみたいに書けて、
Erlangのように動く言語があればいいんじゃない?」と考えた人がいたわけです
それがまさに「Elixir」でして、冒頭でも書いたように、
Elixir最大のポイントは「こう書けるとうれしい」という、
「設計思想」を優先して生まれた言語である、ということなのです(`・ω・´) b
ElixirでBrainF**kインタプリタを書いてみる
先ほどの本を読んだ感じ、設計思想にはものすごく共感できたので、
とりあえず私にとって「関数型言語におけるFizzBuzz」である、
「BrainF**k」の実行系を書いてみました...φ(・ω・`)
以前もScalaで書いたことがありますし・・・
・・・それより前にはRubyで「実行系を作成する実行系」を書いたりもΣ(・ω・ノ)ノ
- ネタプログラミング言語クリエイターYouma (Gunma.web #8) - ぱろっと・すたじお
- ネタプログラミング言語クリエイター Youma
- BF風言語解析器 Windstorm
で、今回Elixirで実際に書いてみたのがこちらです(´・ω・)っ
大きなポイントは二点ありまして、まず一つ目は
「データの処理の流れを記述する」ということを強く意識させる文法です
def execute(code) do # codeを実行するとは・・・ code # 受け取ったcodeを・・・ |> parse # 解釈し・・・ |> eval # 実行することである end defp parse(str) do # 解釈するとは・・・ str # 受け取った文字列を・・・ |> String.replace("\n", "") # 改行を取っ払って・・・ |> String.codepoints # 一文字ずつに分離し・・・ |> List.foldl([], fn c, li -> li ++ [@cmap[c]] end) # コマンドのストリームに置き換えることである end
この書き方はとてもいいですね(`・ω・´) b
「|>」は前の関数の結果を次の関数の第一引数として渡す、
というだけの構文でしかないのですが、
これだけで「何かしたいのか?」がとても明確になります
それこそが「関数型言語風に宣言的なコードを書く」ことの、
最大の利点であり、それ自体は他の言語でも享受できるのですが、
文法として明確に組み込まれているのがポイントです
もう一点が、「関数の引数自体がパターンマッチになっている」ことです
defp step(:pinc, coms, jump_map, ind, cind, pind, buf, result) do step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind+1, buf, result) end defp step(:pdec, coms, jump_map, ind, cind, pind, buf, result) do step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind-1, buf, result) end # 略 defp step(_, _coms, _jump_map, _ind, _cind, _pind, _buf, result), do: result
Javaでいうオーバーロードに近いのですが、
ScalaやRubyでいうところのcaseやswitchのような、
パターンマッチングを、関数の定義自体でやっています
先ほど挙げた、以前書いたScalaコードを見てもらえばわかるのですが、
stepに相当するところの中でパターンマッチングしていて、
また自身を再帰的に呼び出しています
そのため、stepの処理自体が肥大化しているし、
新しいコマンドを追加する場合、case文のマッチングを増やさないといけません(´-ω-)
その点、Elixirのコードは一つ一つの関数が「定義」として小さくまとまっており、
「何をしたいのか?」が明確になって、見通しが良くなっています
「これだけのこと」ではあるのですが、
それでも「こう書けるととても楽」というのを優先したのがElixirでして、
まさにそれはRubyと同様の設計思想なわけです(`・ω・´)
Elixirは「使える」のか?
こんな感じで、私の好みにあうElixirですが、
今の私の仕事である「アーキテクト」の立場からすると、
少なくとも今は「ない」かな・・・とΣ(゚Д゚)ガーン
弊社CTOとも話をして、「この言語はいいね(`・ω・´)」という話にはなっていますが、
あくまでお仕事として考えると、あまりにもコストが高いかなと思います
「表面的な文法」はRubyに似ているため、
例えばRubyの初期のように「PerlのようにRubyを書く」のと同様、
「RubyのようにElixirを書く」のは可能ですし、私もそのように導入します
しかし、Elixirの利点を最大限の享受するには、
「関数型的な設計思想」が必須になります
ElixirがRubyに強く影響を受けているのと同様に、
ElixirのWebフレームワークである「Phoenix Framework」は、
とてもRailsの影響が強いです
「ある程度わかっている人ならば」Railsの仕組みをこれに移すことも可能かもしれません
しかし、RubyもRailsも最大のポイントは、先ほど書いたように、
「時代の要請についていくセンス」と
「変化を許容し、時に古いものを切り捨てる強さ」です
RubyはどんどんVMが高速化されていますし、
文法的にも「型」を導入しようとしています
(普通の言語なら危険信号なのですが、まつもとさんならまず大丈夫でしょう)
Railsもモジュール化・スリム化(API専用モードなど)が進み、
並行処理を意識したライブラリを組み込んだりもしています
なにより、個人的な感覚では、「ElixirでRDBにアクセスすること」自体が、
せっかくのメリットを台無しにしていると思いますΣ(・ω・ノ)ノ
Node.jsの仕事をしたときに強く感じたのは、
とにかく「全てを非同期に書かなければならない」ということであり、
「同期的な処理をどう切り離すか?」を常に考えないといけません
こういったことを意識しなければならないのは、
少なくとも「普通のWebシステム」においてはオーバースペックですし、
例えばRubyでもそういった設計思想のシステムは十分組めます*4
あとは人的コストの問題です
Node.jsを書いたことがある人はそこそこ探せますし、
Rubyを書ける人なら(質を問わなければ)十分見つかるでしょう
(なぜ、FBような大規模システムで、PHPが使われているのかを考えてみてください)
今後、Elixirが何らかの形でメインストリームに乗った場合*5は別として、
現時点では「あえてRubyを切り捨てる」メリットがあまりに薄いのかな・・・と思います
メインのWebサーバをRuby+Railsで組み、
リアルタイム性を求められる領域をNode.jsで構築する、というのが、
今の時点では「普通にいけそうなWebシステム(=長く運用できるシステム)」だと思います
逆に「普通じゃないWebシステム」、
例えば「大量の動画をバックエンドでリアルタイムにエンコードし続けるシステム」ならば、
かなり有効に作用すると思います
・・・が、そういうシステムを必要とする会社は限られてますし、
そういう会社はシステムにかけられるコストが桁違いなので、
やはり「普通の会社」では難しいですよね・・・(´・ω・`)
<おまけ>
今回の記事を書く前、本を読み終わった後にTwitterで連投したものをメモしておきます...φ(・ω・`)
|ω・`).。oO( なんとなく話題のElixir、何がメリットでどういう文脈で使うべきなのかは本でざっくり理解した その上で、これを適切に使えるようなシステムは、(コストなどもふまえた上で)今の仕事の範疇にないかな・・・ 無理矢理作ることはできるけど )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( 「現実的に使える関数型言語」としては非常に良くできているし、Rails風のフレームワークも完成度が高い その上で、そのフレームワークで一般的な=同期I/OのDBにアクセスする時点で、Elixirの利点が死んでしまう これはnode.jsでも同じ )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( 肝になるのは「軽いプロセスを多数起動して協調動作させる」ってところで、要は話題のサーバレスアーキテクチャ=関数型的な処理を、クラウドでさせるのか、Erlangのプロセス群にさせるのかって話 )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( 言いかえれば、(多くの関数型的アーキテクチャ同様)「どこまで副作用がある部分を切り離せるか?」がポイントで、通常のWeb=LAMP的システム設計だと、保存=副作用を起こすことが主目的なので、根本から考え直さないといけない )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( 逆に、WebSocketのような非同期の処理をさせるならたぶん向いている その場合、node.jsが競合になるけど、「現時点では」node=JSを書ける人の方が圧倒的に多く、運用事例も多いので、「今は」nodeを選んじゃうかな・・・ )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( それこそ某D社の事例のように、「バックエンドで大量のエンコードを並行で走らせる」なんて状況なら完璧に向いているけど、そんなバッチ処理は普通のWebサイトだとなかなかないですよね・・・という )
— ぱろっと (@parrot_studio) January 16, 2017
|ω・`).。oO( よって、個人的にはものすごくよくできていると思うけど、お仕事の観点だとなかなか難しいかな・・・という感じ Rubyがわかり、かつ関数型言語を何か知っている人なら理解は早いと思ふ )
— ぱろっと (@parrot_studio) January 16, 2017