田舎で並行処理の夢を見る

試したこと、需要がないかもしれないけど細々とアウトプットしてます

【Golangとの比較あり】新たなプログラミング言語の選択肢としてElixirをオススメしたい理由

elixir-lang.org

1分ぐらいで分かるElixirの長所短所

長所編

Elixirは動的型付けの関数型言語です。Haskellのようにガチガチな厳しい仕様ではなく、関数型言語でありながら同じ変数に対して再代入(正しくは再パターンマッチ)することが可能です(個人的に非推奨)。

# 変数を定義
num = 20201008
# 出力
IO.puts(num)

# 値を更新
num = 20191008
IO.puts(num)

つまりは「ゆるめの関数型言語」とでも思っていただければ大丈夫です。関数型言語の取っ付きにくさを感じることはほとんどなく、関数型言語のエントリーとしてオススメです。

また、Elixirは長い歴史を持つ電話交換機のシステムに用いられていたエリクソン社開発のErlangという言語をベースに、Rubyの持つモダンでストレスフリーの文法を混ぜ合わせて作られた2011年生まれの言語です。Erlangの実行環境であるErlangVM上で動作するため、Erlangを持つ高い耐久性と軽量プロセスによる並行性を保ちつつ、Rubyライクな文法で記述することが出来ます。

ja.wikipedia.org

Elixirだと並行処理が恐ろしく簡単に記述することが出来ます。

# 送信元のidを取得
sender = self()

# プロセスを起動して処理を実行させる
spawn(fn -> 
  # 文字列を出力
  IO.puts("[Info] Welcome to Elixir")
  # 終了したことを立ち上げ元のプロセスに通知
  send(sender, :ok)
end)

# 起動させたプロセスから終了のメッセージを受け取る
receive do
  :ok -> IO.puts("[Info] finished job")
end

実行結果

[Info] Welcome to Elixir
[Info] finished job

短所編

速度

Elixirは決して速い言語ではありません。よく引き合いに出されるGoogle産のGolangには速度ではまず敵いません。単純に動作の速い言語を求めているのであれば、ElixirよりもGolangRustを習得するのが良いでしょう。

型指定

Elixirには型指定がありません。「型があるから良い、悪い」とは言い切ることは出来ませんが、近年の動向としてはTypeScriptをはじめとした型指定のある言語がトレンドです。ただ、dialyzerというモジュールを使うことで型チェックを行うことは可能です。

コミュ二ティ

PythonRuby, Golangなどのコミュニティと比べるとElixirのコミュニティはまだまだ日本では小さく、競技人口も少なく、基本的にドキュメントをはじめとした情報は英語ベースのものが多いです。とは言ったものの、近年ではfukuoka.exさんをはじめ、日本各地でElixirを扱うコミュニティが活動をしており、Qiitaを中心に日本語情報が増えつつあります。また、まだまだ未開拓の分野が多いので、Elixirというプロジェクトにコミット出来るチャンスは十分にあります。

fukuokaex.connpass.com

私の運営している清流elixirもよろしくお願いします🙇‍♂️
elixir-sr.connpass.com

近年の言語トレンド(執筆時: 2020年)

Web業界では先ほども記述した通り、型指定を持つ静的型付け言語が人気なようです。TypeScriptGolangの伸びを見ても明らかです。また、世間の動向を見てみるとオブジェクト指向スクリプト言語といったワードの人気が明らかに減っていることが分かります。関数型言語の需要が増えてきたというよりは、オブジェクト指向に対する世間の見方が変化しているというのが実情のようです。

参照 f:id:takamizawa46:20200315112419p:plain

map, filter, reduceをはじめとする関数型言語由来の関数が多くのスクリプト言語に逆輸入されている現実もあります。関数型言語を始める時期としては決しては悪くない時期になったと考えることが出来ます。

よく比較されるGolangとの住み分け

まず、先ほども記述したように速度ではGolang敵いません。そのため、現時点ではビッグデータ処理や多くの計算量が求められる計算処理には不向きと言えます。その不変な事実がある前提で自分がElixirを推す理由は5つあります。

1.豊富なパッケージとパイプライン演算子

Erlangの文法は少しクセがありますが(慣れの問題)、ElixirRubyライクな文法であるため、Rubyを書いたことがある方ならElixirのコードが初見であっても、スラスラと読むことが出来るでしょう。とは言ったものの、Golangの文法も非常にシンプルなもので、これだけでは差別化要因にはなりません。

しかし、Golangには配列の要素の合計値を算出するためのsum関数といったようなものが言語に実装されておらず、都度、自分で実装する必要があります。その点、ElixirにはEnumをはじめとした非常に強力なパッケージが言語に実装されております。例としてEnumにはmap, filter, reduceが実装されており、importなしでグローバルに呼び出すことが可能です。

またElixirにはパイプライン演算子と呼ばれる非常に強力な文法が実装されています。戻り値を次の実行関数の第1引数として、カリー化して自動で受け渡してくてくます。このパイプライン演算子Enumのコンボが非常に強力で書いていてチョー楽しいです。

Elixir

# Enumとパイプライン演算子の組み合わせ
pipeline = Enum.map(1..100, fn n -> n end) # [1..100]の要素を持つ配列を作成
            |> Enum.map(fn n -> n * 2 end) # 各要素を2倍に
            |> Enum.filter(fn n -> n < 50 end) # 50以下の要素を排除
            |> Enum.reduce(0, fn n, acc -> acc + n end) # 残った要素を合計
            
IO.puts(pipeline)
# 600

Golang

package main
import "fmt"
func main(){
    sum_ := 0
    for i := 1; i < 101; i++ {
      val := i * 2
      if val < 50 {
        sum_ += val
      }
    }
    
    fmt.Println(sum_)
}

// 600

実行速度の話は一旦置いておいて、Elixirで記述した処理は何をしているかが文法を知っていれば一眼で理解出来ます。改修も楽に行うことが出来ます。不要になったパイプラインの一部の処理を除去(もしくは追加)するだけで良いのです。

# Enumとパイプライン演算子の組み合わせ
pipeline = Enum.map(1..100, fn n -> n end) # [1..100]の要素を持つ配列を作成
            |> Enum.map(fn n -> n * 2 end) # 各要素を2倍に
            |> Enum.reduce(0, fn n, acc -> acc + n end) # 残った要素を合計
            
IO.puts(pipeline)
# 10100

2.並行処理とスレッド・プロセス管理のしやすさ

Golangでは軽量スレッド(共有メモリ)とchannel(チャネル)と呼ばれるデータ構造を用いて、スレッド間で通信を行い並行処理を行います。それに対してElixirは軽量プロセス(分散メモリ)とmessage passing(メッセージパッシング)を用いてプロセス間でメッセージを送り合うことで並行処理を行います。

共有メモリである場合に、例えば集計用の変数の値がいつ書き換えられるか分からない、誰が書き換えたのかを判断するのが難しいです。つまりchannel(チャネル)経由以外でのデータ書き換えが可能になってしまします(非推奨だと思いますが...)。

それに対してElixir関数型言語であり、変数の値は不変(immutable)であるため、集計用の変数の値を参照して書き換えるというような処理は記述不可能です。並行処理による思わぬ処理をそもそもの言語仕様によって防ぐことが出来ます。

またElixirにはsupervisorと呼ばれる他プロセスの監視に特化したプロセスが実装されており、例えば、1つでもプロセスが死んだ場合に、全てのプロセスを立ち上げ直すといった戦略(strategy)を簡単に設定可能です。また、特定のプロセス間同士でのみ、お互いの生存確認を監視したりとプロセスの管理が手軽で強力です。プロセスの監視が重要になるアプリケーションではElixirが非常に強力な力を発揮するでしょう。

Golangにはsupervisorのようなものはなく、channel(チャネル)を用いて、フルスクラッチで実装する必要があります。外部パッケージがあれば話は別です。

3.関数の引数によるパターンマッチ

Elixirには代入という考え方はなく全ての評価はパターンマッチです。そんな話は置いておいて、非常に強力な関数の引数を用いたパターンマッチが実装されています。以下のサンプルをご覧ください。このように同名の関数が何度でも記述可能で第1引数の値によって処理を分岐させることが出来るのです(マッチ順は記述順です)。

defmodule Sample do
  def price("apple"), do: 120
  def price("banana"), do: 110
  def price("grape"), do: 230
  def price("melon"), do: 1200
  def price(_), do: 100 # apple, banana, grape, melonのどれにも該当しない場合
end

Sample.price("apple") |> IO.puts()
Sample.price("banana") |> IO.puts()
Sample.price("grape") |> IO.puts()
Sample.price("melon") |> IO.puts()
Sample.price("orange") |> IO.puts()

実行結果

120
110
230
1200
100

分岐が一眼で分かる上に拡張性も非常に高いです。仮にorangeの価格が120円になった場合にはdef price("orange"), do: 120という1行を追加するだけで完了です。この関数の引数のパターンマッチを使うことで再帰関数を用いた処理を記述することも可能です。詳しくは解説しませんが、配列が空の場合とそうではない場合で実行する関数を分岐させています。

defmodule Calc do
  def sum(lst), do: _sum(lst, 0)
  defp _sum([], accum), do: accum
  defp _sum([head | tail], accum) do
    _sum(tail, head + accum)
  end
end

IO.puts(Calc.sum([1,2,3,4,5])) 
# 15

4.強力なWebフレームワーク

RubyRuby in railsという強力なWebフレームワークがあります。Rubyがこれまで広がった理由の1つとして「Railsが世に登場したから」と言われています。ElixirにはRuby on railsをベースにRailsのコミッターが作成したPhoenixというWebフレームワークがあります。Railsと共通する部分が多いのですが、Railsで煩わしかった部分が改善されており、内部化された部分が少なくなっています(Rails程、全てをよしなにやらない)。
また、Elixirの軽量プロセスと並行処理を活かしたWebsocketがデフォルトで用意されていたり、フロントをJavaScriptなしで記述可能なLiveViewといった機能もリリースされています。

hexdocs.pm

GolangではデフォルトパッケージであるhttpginのようなWebフレームワークを使って、マイクロサービスや単一エンドポイントのバックエンドの開発を気軽に行うことが出来る一方で、RailsのようなWebフレームワークが存在しておらず、大規模なアプリケーション開発には向いていないと言われています。

Webでの大規模アプリケーション開発を視野に入れたいのであれば、Elixirを選択肢として考えてみてはいかかでしょうか。

5.IoTとの相性

ElixirNervesというパッケージを用いることで、ラズベリーパイ上で動作可能です。Elixirの強力なNode間通信、プロセス管理と組み合わせることでIoTのプラットフォームを簡単に組み上げることが可能でしょう。

nerves-project.org

35分頃からデモがあります。LTをしているのはNervesプロジェクトの中心メンバーであるJustinさんです。
www.youtube.com

最後に

まだまだElixirにまつわるトピックはあるのですが、今回はこれぐらいにしておきます。Elixirの良さ、強み、決して万能ではないということが伝わっていえば嬉しい限りです。もし、どれか1つでも心に響くものがあったり、「この言語仕様なら、今悩んでいる点を解決できるんじゃ?」と思い当たる点があればElixirを選択肢として、ぜひ考えてみて下さい。

最後になりましたが、Elixirのオススメの入門書として「プログラミングElixir」という書籍(3000円程度)があります。中々にコアな書籍なので売っている場所は限られてきますが現段階で入門書としてはこの一冊がデファクトスタンダードと言えるでしょう。

プログラミングElixir

プログラミングElixir

  • 作者:Dave Thomas
  • 発売日: 2016/08/19
  • メディア: 単行本(ソフトカバー)

おまけ: 僕がElixirを始めるまでの話

今まで新卒エンジニアとして頑張ってきたつもりです。元々、専攻が土木・建築であったため、ゼロからのスタートでした。プログラミングだけに限らず多くの事を見て、体験して学んできました。エンジニアという立場ではフロントエンドにバッグエンド、はたまた機械学習にまで広く関わりました。

そんな日々を送っていて毎日の様に「自分のレベルの低さ」に絶望しています。目を疑う様な経歴、学歴を持つ人達、コンピューターサイエンスに深い理解のある人達、難解な処理を難なく実装してしまう人達など...上には上がいるんだなぁと思わされます。

f:id:takamizawa46:20200314232443p:plain:w400

また職場の同期は元々、国立大の博士課程で理論物理学(ブラックホール量子コンピュータだって)を学んでいたというバケモノです。僕が1ヶ月かけて理解することが彼には数日で理解出来ます。元々、彼がとんでもない努力をしていたという話は聞いていますが、同じ業務に関わる以上は引け目は感じてしまいます。

この先、こんな人達と対等に戦っていくのは正直、無理だと思いました。エンジニアとして生き残るためには戦略が必要だと悟りました。

考えるに考えた結果「時間をかければ何とか理解できるのだから、学習開始の日を早めれば良い」という結論に至りました。それからはブルーオーシャンを探しました。今まで本腰入れて書いてきたのはPythonです。ただ、Pythonは競技人口が多すぎるのと、用途が計算科学や機械学習がメインであり、同期のような人達が集まりやすいと判断しました。ただ、需要が高い言語なのでPythonをやめるつもりはありません。

そのため、今から習得することで先駆者になれるチャンスがあり、将来性がある言語を探すことにしました。そしてある日(2019/03)、出会ったのがElixirという言語です。

参考文献