この記事は、fukuoka.ex Elixir/Phoenix Advent Calendar 2019 の 10日目です。 昨日は @tomoaki-kimura さんの「Elixir で OAuth2 を使って、 STRAVA のApiを叩いてみたい ①」でした。
前置き
この記事は社内LTを記事にしたものです。
記事ではいくつかのパートを飛ばしています。
Elixirとは
- Erlang VM(BEAMとも言う)上で動作する関数型/動的型付け/並行志向なプログラミング言語
- Rails Core CommitterだったJose Valimを始めとする6人によって開発/保守されている
- 耐障害性/並行性/スケーラビリティが特徴
- プロセス(Erlang VM上のプロセス)をたくさん使ったプログラミング
- Rubyにsyntaxが似ている
- Rubyを使っているのであれば、抵抗感なく始められると思う。
- ただし、syntaxが似ているだけで、他は全然違うので、Rubyと似ていない。
- 2011年にリリース
- 2019年10月時点では1.9.2が最新バージョン
並列プログラミングをやってみよう!
今回はErlang & Elixir Fest 2019にて行われたHandsOn資料を解説していく形で進めます。
やること
- 1000以下のランダムな数を生成し、その時間(ミリ秒)スリープした後に、その数字を返す
- 1の処理を10回実行する
- 1の処理を100回実行する
- 1の処理を10回、並行に実行する
- 1の処理を100回、並行に実行する
- 1の処理を10000回、並行に実行する
- 2〜6の実行時間を比較する**(並列のすごさと簡単にできることを感じて欲しい)**
コードの説明
まずは「1000以下のランダムな数を生成し、その時間(ミリ秒)スリープした後に、その数字を返す」を実現している関数について書きます。
https://github.com/ohr486/ErlangElixirFestHandsOn2019/blob/master/lesson/lib/lesson.ex#L27
def sleep_rand(n) do
sleep_time = :rand.uniform(n)
:timer.sleep(sleep_time)
#IO.puts "#{sleep_time}ミリ秒sleepしました"
sleep_time
end
この関数ではErlangモジュールである**:randと:timer**を使っています。ElixirではErlangのモジュールをAtomとして呼び出すことができます。
:rand.uniform/1は1 <= m <= nの範囲からランダムな値を返します。nは引数に取った数字のことです。
:timer.sleep/1は引数に取った値分、処理を止めます。単位はmilisecondです。返り値は**:okです。
で、最後に関数の返り値として何ミリ秒処理を止めたかを返すためにsleep_time**を返り値としています。
次に「1の処理を10回実行する」「1の処理を100回実行する」を実現している関数について書きます。
https://github.com/ohr486/ErlangElixirFestHandsOn2019/blob/master/lesson/lib/lesson.ex#L36
def exec_seq(n) do
IO.puts "=== 開始: #{n}回rand_sleepを繰り返す ==="
result = Enum.map(1 .. n, fn(_) -> sleep_rand(1000) end) |
IO.puts "=== 終了: #{n}回rand_sleepを繰り返す ==="
result
end
IO.puts/1は引数に取った値を標準出力に出力する関数です。
Enum.map/2は第一引数にrangeを取り、第二引数に関数を取ります。返り値は第二引数に取った関数の返り値を配列にしたものです。
このケースにおいては第二引数に取った無名関数を1~n回分繰り返し、実行する。無名関数の返り値である整数値をを配列にして返しています。
最後にEnum.map/2の返り値をこの関数の返り値としています。
次に「1の処理を10回、並行に実行する」「1の処理を100回、並行に実行する」「1の処理を10000回、並行に実行する」を実現している関数について書きます。
def exec_pal(n) do
IO.puts "=== 開始: #{n}回rand_sleepを繰り返す(並列) ==="
list = Enum.map(1..n, fn(_) -> Task.async(Lesson, :sleep_rand, [1000]) end)
result = Enum.map(list, fn(d) -> Task.await(d) end)
IO.puts "=== 終了: #{n}回rand_sleepを繰り返す(並列) ==="
result
end
Task.async/3とTask.await/2という関数が初めて出てきたので、この関数とTaskモジュールの説明を中心に行います。
Taskモジュールとは以下のことをやっています。
Taskは、他のプロセスとほとんどまたはまったく通信せずに、
多くの場合、その存続期間を通じて特定のアクションを実行することを意図したプロセスです。
Taskの最も一般的な使用例は、値を非同期的に計算することにより、
順次コードを並列コードに変換することです
すごいざっくり言えば、「簡単に順次処理を行うコードを並列処理を行うコードに書き換えられる」です。
Task.async/3は第一引数にモジュール名、第二引数にそのモジュールに定義されている関数、第三引数にその関数に渡す引数を取ります。返り値はTask構造体です。
Task構造体とは以下のような構造体です。
iex(28)> task = Task.async(:rand, :uniform, [100])
%Task{
owner: #PID<0.104.0>,
pid: #PID<0.135.0>,
ref: #Reference<0.2831767453.3135242244.211643>
}
それぞれの属性について以下に説明を書きます。
- owner
- Task.async/3が生み出すプロセスの親プロセスのID
- pid
- Task.async/3が生み出すプロセスのID
- ref
- Erlnag VM上でユニークな値であり、親プロセスが子プロセスを監視するときに生成される値
プロセスの監視についてはここでは説明しません。詳しく知りたい方はプログラミングElixirを読みましょう。
Task.async/3でTask構造体を返すときにはすでにバックグラウンドで引数に取った関数の処理が始まっています。Task.await/2ではその処理結果を呼び出すことができます。
なので、Lesson.sleep_rand/1の処理がバックグラウンドで開始されて、処理が終わっている場合はその返り値をすぐに返し、処理が途中の場合は処理が完了するまでの間待ちます。処理が終わると、次のTaskの結果を返させます。
この処理をバックグラウンドで行わせて、処理結果を簡単に取り出せるのがTask.async/3とTask.await/2の良いところだと思っています。
実際に順次処理と並列処理を見比べてください。きっとElixirを使いたくなります。
下のgif画像は左画面が並列処理、右画面が順次処理です。
採用事例
聞いたことがあるところは以下です。いくつかの会社はもともとRubyを使っていて、Elixirに移行したようです。事例としてはゲームの裏側のサービスが多い印象を受けています。しかし、mixiでは決済アプリの裏側でElixirが動いているとの情報を目にしたこともあります。これです。
- Akatsuki(ゲーム)
- Drecom(ゲーム)
- gumi(ゲーム)
- mixi(ゲーム/スポーツ←おそらくチャット&ライブ中継)
- Discord(チャット)
- ABEJA(機械学習プラットフォーム←Webサービス)
弊社で使うならこう使う
弊社では一人で先輩たちに「Elixirやりましょうよ〜〜」と言っていたのですが、以下のような質問が多かったです。
Q. Railsでよくね??
Q. Goでよくね??
それに対する私が思う答えを書きます。
Q. Railsでよくね??
A. ケースバイケースです。
チャットやゲームのようなリアルタイムな通信を求められるサービスはElixir(Phoenix)だと楽みたいです。
mixi/Drecom/Akatsuki/任天堂のようにRubyを使ってゲーム開発をしていた会社がElixirに移行しています。そういうことなのでしょう。この辺はRailsのActionCableと比較してみて、どっちが良いのか?(開発効率や性能を考慮して)判断するのが良さそう。
Q. Goでよくね??
A. Goは導入コストが高そう。Goを知らないので、何とも言えないです。次はGoやkotlinを触ってみます。個人的にはRubyやっている人が多い会社はGoよりもElixirの方がとっつきやすそうでいいんじゃないのかな?と思っています。
まとめ
- 開発する上でRuby(Rails)よりもElixir(Phoenix)の方が優れている箇所はいくつか見られる。なので、サービスの全ての箇所をRuby/Railsで開発するのではなく、より良い手段としてElixir(Phoenix)があるなら、それを使いたい。
- そういう意味ではElixir(Phoenix)にはこだわりはない。他に良い言語/フレームワークがあるならば、それを使おう。
- こだわりはないが、リアルタイム性が求められるシステムなどでは使ってみたい気持ちはある。その時はRailsのActionCableとかと比較して、採用する基準を示そうと思う。
- もっと精進しなければならない。
余談
- Elixirの命名について
- Elixirと言われると、FinalFantasyの回復する薬を思い出すし、他のライブラリとかもなんかFF由来っぽさそうなのが多いので、FFから取っているのかと思いきや、そうではないらしい。fukuoka.exの人がJose Valim氏にそう聞いたらしい。
- 日本のコミュニティについて
- gumi/Drecom/Akatsukiの三社がよくイベントをやっているイメージがある。特にgumiのdev.toのブログはとてもお世話になっている。
- 会社以外のコミュニティだと、fukuoka.exやtokyo.beamはお世話になっている。fukuoka.exは幅広い人が参加しているのに対して、tokyo.beamは強者が多い印象がある。別にどちらが良いというわけではない。他にもコミュニティはあるのだが、参加したことがないので、コメントはできない。
- Erlangとの関係性について
- なんとなくElixirを趣味で書くならば、別にやらなくても良いと思う。ただ、ちゃんと業務で使うならば、Erlangはやった方が良いと思う。理由としてはBEAMのことを知るためである。ElixirはBEAM上で動作する言語なので、BEAMのことを知る必要があるが、そのためにもErlangは知っといて損はなさそう。
- 勉強方法について
- プログラミングElixirは読んだほうが良い。日本語版は古いが、英語版も1.6対応の書籍しかないので、別に気にしなくても良いと思っている。
- Programming Phoenix 1.4はとても良い本(Web開発をするならば必読ではなかろうか)。
- まだ読んでないが、Programming EctoというORマッパーライブラリについての本があり、これも必読な気配を感じている。
- Stuff Goes Bad Erlang in Angerこれも読んだほうが良いらしい。2年ぐらい前?に有志で日本語化された。
- BEAMについてはこれが詳しく書いてあるとのこと。
- しんどいことについて
- 英語の情報が多いこと。書籍にしろ公式にしろ、深い情報にしろほぼ全てが英語です。なので、英語に対して抵抗感が強いと、きついです。抵抗感はないけど、読むのが苦手な人もきついです。
終わりに
弊社でElixirをやるかどうかは分かりませんが、Elixirはおもろいので、趣味として触っていきたいと思います。
明日は @takamizawa46 さんの記事です。