1分ぐらいで分かるElixirの長所短所
長所編
Elixir
は動的型付けの関数型言語です。Haskell
のようにガチガチな厳しい仕様ではなく、関数型言語でありながら同じ変数に対して再代入(正しくは再パターンマッチ)することが可能です(個人的に非推奨)。
# 変数を定義 num = 20201008 # 出力 IO.puts(num) # 値を更新 num = 20191008 IO.puts(num)
つまりは「ゆるめの関数型言語」とでも思っていただければ大丈夫です。関数型言語の取っ付きにくさを感じることはほとんどなく、関数型言語のエントリーとしてオススメです。
また、Elixir
は長い歴史を持つ電話交換機のシステムに用いられていたエリクソン社開発のErlang
という言語をベースに、Ruby
の持つモダンでストレスフリーの文法を混ぜ合わせて作られた2011年生まれの言語です。Erlang
の実行環境であるErlangVM
上で動作するため、Erlang
を持つ高い耐久性と軽量プロセスによる並行性を保ちつつ、Ruby
ライクな文法で記述することが出来ます。
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
よりもGolang
やRust
を習得するのが良いでしょう。
型指定
Elixir
には型指定がありません。「型があるから良い、悪い」とは言い切ることは出来ませんが、近年の動向としてはTypeScript
をはじめとした型指定のある言語がトレンドです。ただ、dialyzer
というモジュールを使うことで型チェックを行うことは可能です。
コミュ二ティ
Python
やRuby
, Golang
などのコミュニティと比べるとElixir
のコミュニティはまだまだ日本では小さく、競技人口も少なく、基本的にドキュメントをはじめとした情報は英語ベースのものが多いです。とは言ったものの、近年ではfukuoka.ex
さんをはじめ、日本各地でElixir
を扱うコミュニティが活動をしており、Qiita
を中心に日本語情報が増えつつあります。また、まだまだ未開拓の分野が多いので、Elixir
というプロジェクトにコミット出来るチャンスは十分にあります。
私の運営している清流elixir
もよろしくお願いします🙇♂️
elixir-sr.connpass.com
近年の言語トレンド(執筆時: 2020年)
Web業界では先ほども記述した通り、型指定を持つ静的型付け言語が人気なようです。TypeScript
、Golang
の伸びを見ても明らかです。また、世間の動向を見てみるとオブジェクト指向
やスクリプト言語
といったワードの人気が明らかに減っていることが分かります。関数型言語
の需要が増えてきたというよりは、オブジェクト指向
に対する世間の見方が変化しているというのが実情のようです。
map
, filter
, reduce
をはじめとする関数型言語由来の関数が多くのスクリプト言語に逆輸入されている現実もあります。関数型言語
を始める時期としては決しては悪くない時期になったと考えることが出来ます。
よく比較されるGolang
との住み分け
まず、先ほども記述したように速度ではGolang
敵いません。そのため、現時点ではビッグデータ処理や多くの計算量が求められる計算処理には不向きと言えます。その不変な事実がある前提で自分がElixir
を推す理由は5つあります。
1.豊富なパッケージとパイプライン演算子
Erlang
の文法は少しクセがありますが(慣れの問題)、Elixir
はRuby
ライクな文法であるため、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フレームワーク
Ruby
にRuby in rails
という強力なWebフレームワークがあります。Ruby
がこれまで広がった理由の1つとして「Rails
が世に登場したから」と言われています。Elixir
にはRuby on rails
をベースにRails
のコミッターが作成したPhoenix
というWebフレームワークがあります。Rails
と共通する部分が多いのですが、Rails
で煩わしかった部分が改善されており、内部化された部分が少なくなっています(Rails
程、全てをよしなにやらない)。
また、Elixir
の軽量プロセスと並行処理を活かしたWebsocket
がデフォルトで用意されていたり、フロントをJavaScript
なしで記述可能なLiveView
といった機能もリリースされています。
Golang
ではデフォルトパッケージであるhttp
やgin
のようなWebフレームワークを使って、マイクロサービスや単一エンドポイントのバックエンドの開発を気軽に行うことが出来る一方で、Rails
のようなWebフレームワークが存在しておらず、大規模なアプリケーション開発には向いていないと言われています。
Webでの大規模アプリケーション開発を視野に入れたいのであれば、Elixir
を選択肢として考えてみてはいかかでしょうか。
5.IoTとの相性
Elixir
はNerves
というパッケージを用いることで、ラズベリーパイ上で動作可能です。Elixir
の強力なNode間通信、プロセス管理と組み合わせることでIoTのプラットフォームを簡単に組み上げることが可能でしょう。
35分頃からデモがあります。LTをしているのはNerves
プロジェクトの中心メンバーであるJustin
さんです。
www.youtube.com
最後に
まだまだElixir
にまつわるトピックはあるのですが、今回はこれぐらいにしておきます。Elixir
の良さ、強み、決して万能ではないということが伝わっていえば嬉しい限りです。もし、どれか1つでも心に響くものがあったり、「この言語仕様なら、今悩んでいる点を解決できるんじゃ?」と思い当たる点があればElixir
を選択肢として、ぜひ考えてみて下さい。
最後になりましたが、Elixir
のオススメの入門書として「プログラミングElixir」という書籍(3000円程度)があります。中々にコアな書籍なので売っている場所は限られてきますが現段階で入門書としてはこの一冊がデファクトスタンダードと言えるでしょう。
おまけ: 僕がElixirを始めるまでの話
今まで新卒エンジニアとして頑張ってきたつもりです。元々、専攻が土木・建築であったため、ゼロからのスタートでした。プログラミングだけに限らず多くの事を見て、体験して学んできました。エンジニアという立場ではフロントエンドにバッグエンド、はたまた機械学習にまで広く関わりました。
そんな日々を送っていて毎日の様に「自分のレベルの低さ」に絶望しています。目を疑う様な経歴、学歴を持つ人達、コンピューターサイエンスに深い理解のある人達、難解な処理を難なく実装してしまう人達など...上には上がいるんだなぁと思わされます。
また職場の同期は元々、国立大の博士課程で理論物理学(ブラックホールと量子コンピュータだって)を学んでいたというバケモノです。僕が1ヶ月かけて理解することが彼には数日で理解出来ます。元々、彼がとんでもない努力をしていたという話は聞いていますが、同じ業務に関わる以上は引け目は感じてしまいます。
この先、こんな人達と対等に戦っていくのは正直、無理だと思いました。エンジニアとして生き残るためには戦略が必要だと悟りました。
考えるに考えた結果「時間をかければ何とか理解できるのだから、学習開始の日を早めれば良い」という結論に至りました。それからはブルーオーシャンを探しました。今まで本腰入れて書いてきたのはPython
です。ただ、Python
は競技人口が多すぎるのと、用途が計算科学や機械学習がメインであり、同期のような人達が集まりやすいと判断しました。ただ、需要が高い言語なのでPython
をやめるつもりはありません。
そのため、今から習得することで先駆者になれるチャンスがあり、将来性がある言語を探すことにしました。そしてある日(2019/03)、出会ったのがElixir
という言語です。