以前から
気になっていた Elixir という言語について、以下の資料を写経してチュートリアルしてみた。
www.slideshare.net
その前に Elixir (エリクサー) とは
そこには Erlang が…
- Erlang の関数を呼び出せるため、Erlang のシステムにシームレスに統合出来る
- Erlang 良い部分(並行性や信頼、耐障害性)をそのまま利用出来る(当然 Erlang の悪い部分も受け継いでしまっているらしい)
特徴
- Elixir は Erlang の仮想マシン(BEAM) 上で動作するコンピュータプログラミング言語
- 関数型言語
- Erlang の関数がほぼオーバーヘッド無しに呼べる
- OTP(Open Telecom Plataform) = Erlang コンパイラとインタプリタや各種ライブラリをバンドルしている
- シンタックスが Ruby と似ている
その他色々… こちらが参考になる。
エコシステム
- ライブラリ管理に
hex
を利用する… Ruby だとgem
- ビルドツールに
mix
を利用する… Ruby だとrake
- 対話型シェルに
iex
を利用する… Ruby だとirb
- Web Application Framework として
Phoenix
がメジャー… Ruby だとRails
で、なんであんたは Elixir をやろうとしたの?
チュートリアル
チュートリアル環境
- OS は Ubuntu Xenial でお届けします
ubuntu@ubuntu-xenial:~$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=16.04 DISTRIB_CODENAME=xenial DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
- Elixir は 1.4.1 でお届けします
ubuntu@ubuntu-xenial:~$ elixir --version Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Elixir 1.4.1
Elixir のインストール
以下を実行して Erlang と Elixir をインストールする。
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang
sudo apt-get install elixir
以下を実行して iex を起動することを確認する。
ubuntu@ubuntu-xenial:~$ iex Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help)
対話シェルで操作してみる
対話シェル iex
を起動して加算や list や map の操作を行ってみる。
iex(1)> 1+2 3 iex(2)> list = [ 123, "abc", 456, true ] [123, "abc", 456, true] iex(3)> Enum.sort( list ) [123, 456, true, "abc"] iex(4)> map = %{ "key2" => "abc", "key1" => 123 } %{"key1" => 123, "key2" => "abc"} iex(5)> Enum.sort( map ) [{"key1", 123}, {"key2", "abc"}] iex(6)> BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
list や map の操作に Enum モジュールを利用している。Enum モジュール には each
や empty?
等の関数が用意されていて、さながら Ruby と見紛う。
関数を定義する
新規プロジェクトの作成
新規プロジェクトを以下のように作成する。
mix new sample
以下のように出力される。
* creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/sample.ex * creating test * creating test/test_helper.exs * creating test/sample_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd sample mix test Run "mix help" for more commands.
関数の作成
lib/sample.ex を以下のように記述する。
defmodule Sample do def sort( values ) do Enum.sort( values ) end end
関数のビルドと実行
記述したら、以下のコマンドを実行してビルドを行う。
cd sample iex -S mix
iex が起動するので、以下のように作成した Sample 関数を実行する。
ubuntu@ubuntu-xenial:/vagrant/elixir/sample$ iex -S mix Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Sample.sort( [ 5, 1, 3 ]) [1, 3, 5] iex(2)> Sample.sort [ 5, 1, 3 ] #=> () は省略可能 [1, 3, 5]
おお。
関数の修正と再ビルド
関数は end
を省略して 1 行でも記載することが出来る。
defmodule Sample do def sort( values ), do: Enum.sort( values ) end
関数を修正したら recompile()
を実行して再ビルドする。
iex(3)> recompile() Compiling 1 file (.ex) :ok
改めて Sample 関数を実行する。
iex(4)> Sample.sort [ 5, 1, 3 ] [1, 3, 5] iex(5)> Sample.sort %{ "key2" => "abc", "key1" => 123 } [{"key1", 123}, {"key2", "abc"}]
map もキー名でソートすることが出来る。
パターンマッチ
引数でのマッチ
Sample 関数に以下を追加する。
def match( %{ Yes: need } ), do: need
追加したら recompile()
する。
iex(7)> recompile() Compiling 1 file (.ex) :ok
以下のように Sample 関数を実行する。
iex(8)> Sample.match %{ No: "-", Yes: "we can", NA: "N/A" } "we can"
マップの Yes
キーの値を関数の引数だけで実装出来る。ちなみに、Ruby でやろうとすると、以下のような書き方になるんだろうか。
irb(main):001:0> def match(args = {}) irb(main):002:1> p args[:Yes] irb(main):003:1> end => :match irb(main):004:0> match({ No: "-", Yes: "we can", NA: "N/A" }) "we can" => "we can"
あー、改めて、関数の引数だけで値が取り出せるのは便利かも。
複数関数の呼び分け
値でもマッチさせることも可能なので、以下を追加して recompile()
する。
def match( %{ Yes: "we can" } ), do: "Barack Obama" # 追加する def match( %{ Yes: need } ), do: need
Sample 関数を実行してみる。
iex(2)> Sample.match %{ No: "-", Yes: "we can", NA: "N/A" } "Barack Obama"
ほー。マップのキーをチェックして処理を分岐することが出来ている。これも Ruby でやろうとすると以下のような書き方になるんだろうか。
irb(main):001:0> def match(args = {}) irb(main):002:1> if args[:Yes] == "we can" irb(main):003:2> p "Barack Obama" irb(main):004:2> end irb(main):005:1> end => :match irb(main):006:0> match({ No: "-", Yes: "we can", NA: "N/A" }) "Barack Obama" => "Barack Obama"
マッチしない場合
キーが存在しない場合には、以下のようなエラーとなる。
iex(4)> Sample.match %{ No: "-", NA: "N/A" } ** (FunctionClauseError) no function clause matching in Sample.match/1 (sample) lib/sample.ex:24: Sample.match(%{NA: "N/A", No: "-"})
以下のように関数の引数に _
を指定すると、その他としてマッチ出来るようになる。
def match( %{ Yes: "we can" } ), do: "Barack Obama" def match( %{ Yes: need } ), do: need def match(_), do: "Yes...Not EXIST" # 追加する
recompile()
して関数を再実行する。
iex(5)> recompile() Compiling 1 file (.ex) :ok iex(6)> Sample.match %{ No: "-", NA: "N/A" } "Yes...Not EXIST"
例の如く、Ruby で書くと以下のようになるんだろうか。
irb(main):010:0> def match(args = {}) irb(main):011:1> p "Yes...Not EXIST" unless args.has_key?(:Yes) irb(main):012:1> end => :match irb(main):013:0> match({ No: "-", NA: "N/A" }) "Yes...Not EXIST" => "Yes...Not EXIST"
関数内でマッチ
関数の引数では無く、関数内でマッチさせる場合には以下のように書く。
defmodule Sample do def match_inner( input_map ) do %{ Yes: need } = input_map need end end
以下のように引数マッチと同じ結果となる。
iex(10)> Sample.match_inner %{ No: "-", Yes: "we can", NA: "N/A" } "we can"
引数マッチ版の方がシンプルな気がする。
リストのパターンマッチ
リストは先頭を head
で、以降を tail
でパターンマッチすることが出来る。
iex(1)> [ head | tail ] = [ 5, 8, 3, 1 ] [5, 8, 3, 1] iex(2)> head 5 iex(3)> tail [8, 3, 1]
わかり易い言葉で書くと head
はリストの最初の要素を取り出すことが出来て、tail
はその残りを取り出すことが出来る。
以下のようにも書くことが出来るようだ。
iex(10)> list = [1,2,3] [1, 2, 3] iex(11)> hd(list) 1 iex(12)> tl(list) [2, 3]
Ruby だと以下のように書くんだろうか。
irb(main):014:0> list = [ 5, 8, 3, 1 ] => [5, 8, 3, 1] irb(main):015:0> list.first => 5 irb(main):016:0> list[-3..-1] => [8, 3, 1]
個人的には Elixir も Ruby も直感的な気がする。各コード量的には微々たるものだけど Elixir の方が少ないかもしれない。
リストのパターンマッチ(2)
head
と tail
を利用することで、リストの巡回(再帰呼び出し)が可能となる。
defmodule Sample do def nums( [ head | tail ], rows ) do #...1 nums( tail, [ head | rows ] ) #...2 end def nums( [], rows ) do #...3 rows #...4 end end
関数を実行すると以下のように出力される。
iex(6)> Sample.nums( [ 5, 8, 3, 1 ], [] ) [1, 3, 8, 5]
少し理解に苦しんだので、以下のように整理した。
- リストの先頭を
head
で、残りをtail
でマッチさせる tail
に入っている残りの値の先頭をhead
でマッチさせる- 2 の
tail
が空になったら 3. が呼ばれで 4. の rows が出力される
正直言って、まだボヤーっとしているので、head
と tail
はもう少し弄る。
ちなみに、Ruby だと以下のように書くのかな…
irb(main):006:0> list = [5, 8, 3, 1] => [5, 8, 3, 1] irb(main):007:0> new_list = [] => [] irb(main):008:0> list.each { |l| new_list << l } => [5, 8, 3, 1] irb(main):009:0> new_list.reverse => [1, 3, 8, 5]
リストのパターンマッチ(2) + 1
もすこし再帰呼び出しをやりたいので、list に格納されている数値の合計を求めてみる。
defmodule Sample do def sum_list( [ head | tail ], total ) do #...1 sum_list( tail, head + total ) #...2 end def sum_list( [], total ) do #...3 total #...4 end end
おお、なんか理解が出来てきたけど整理。
- リストの先頭を
head
で、残りをtail
でマッチさせる(初期値は 0) - 1 の
tail
のhead
とtotal
に格納されている数値を加算(tail
が空になるまで繰り返す) - 2 の
tail
が空になったら 3. が呼ばれて 4. の total が出力される
Ruby だと inject メソッドを使って以下のように書くはず。
irb(main):010:0> list = [5, 8, 3, 1] irb(main):011:0> list.inject(0) { | sum, l | sum + l } => 17
おお、シンプル。
ということで
sample.ex
今回のチュートリアルで作成した sample.ex は以下のようになった。
defmodule Sample do # def sort( values ) do # Enum.sort( values ) # end def sort( values ), do: Enum.sort( values ) def match( %{ Yes: "we can" } ), do: "Barack Obama" def match( %{ Yes: need } ), do: need def match(_), do: "Yes...Not EXIST" def match_inner( input_map ) do %{ Yes: need } = input_map need end # def nums( [ head | tail ], rows ), do: nums( tail, [ head | rows ] ) # def nums( [], rows ), do: rows def nums( [ head | tail ], rows ) do #...1 nums( tail, [ head | rows ] ) #...2 end def nums( [], rows ) do #...3 rows #...4 end def sum_list( [ head | tail ], total ) do sum_list( tail, head + total ) end def sum_list( [], total ) do total end end
Elixir とは (再掲)
- Elixir は Erlang の仮想マシン(BEAM) 上で動作するコンピュータプログラミング言語だが、今回は Erlang を殆ど意識する事が無かった
- 関数型言語
- Erlang の関数がほぼオーバーヘッド無しに呼べるが、こちらも Erlang を呼べなかった
- OTP(Open Telecom Plataform) = Erlang コンパイラとインタプリタや各種ライブラリをバンドルしている
- シンタックスが Ruby と似ているとのことだが、確かに似ていると思う
引き続き…
参考にさせて頂いた資料はまだまだ続くので、引き続き写経を続けていくぞー。
参考
- https://www.slideshare.net/piacere_ex/elixir1json-75571537
- https://ja.wikipedia.org/wiki/Elixir_(プログラミング言語)
- http://elixir-lang.org/install.html#unix-and-unix-like
- http://k-shogo.github.io/article/2015/01/05/lets_start_elixir/
- http://qiita.com/HirofumiTamori/items/0dfdbada30c7d8f183fd
- http://qiita.com/HirofumiTamori/items/b72ad232dc3b26e87a03