業務でElixirに触れる機会があったため、プログラミングElixir、Elixir School 日本語訳、公式ドキュメントを読みつつ、iex上でコードを動かして遊びました。
普段はPHPやRubyなどを触っており、
そもそも関数型言語にまったく馴染みがないためElixirは新鮮でした。
(ちょっとだけApache SparkでScalaに触れたことがありますが・・・)
この記事ではElixir初学者(自分自身)にとって特徴的だった点をまとめました。
NodeやOTP、メタプログラミングについても書きたかったのですが、
いかんせん自分にはまだ難しく深く機能をいじれなかったため、
その辺りはまたの機会に記事にしたいと思います。
Elixirについて
Elixirの特徴についてはElixir School 日本語訳のトップに記載されています。
ElixirはRubyプログラマのJosé Valim氏によって開発されたためか、
シンタックスがRubyライクな感じになっています。
ただそれは見た目だけで、実際の中身についてはErlangに精通していないと深い理解は得られないかもしれないです。
※ElixirはErlangVM上で動作するようになっており、
- Elixirコンパイル→Erlangコンパイル→ErlangVM上にロードして実行
という流れで最終的にはErlangに変換されるからです。
環境整備
わしのマシン
- OS X Yosemite
- Ver 10.10.5
インストール
homebrewで楽をします。
brew install elixir
インストールの確認
$ iex -v
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
IEx 1.3.4
Elixirの実行
コードの動作確認はiexという対話式でコードが実行される環境で行います。
$ iex
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> IO.puts("Get Wild")
Get Wild
:ok
iex(2)>
基本的なデータの型
https://elixirschool.com/jp/lessons/basics/basics/#section-2
ここを一通り見ておけばOKかと思います。
パターンマッチ
iex(1)> getwild = 1
1
iex(2)> getwild + 2
3
iex(3)>
このような単純なコードを見たときに、直感的には
「変数に1を代入し、2を加える」
と思うのですが、Elixirでは違う考え方をするようです。
Elixirにおける =
は代入とは考えず、=
はマッチ演算子と呼ばれ、上記コードの1行目の例で言うと、getwildに1が 束縛
された(マッチされた)と考えるそうです。
(なんやそれ...)
iex(node_one@kodamasouichirou-no-MacBook-Air)29> getwild = 1
1
iex(node_one@kodamasouichirou-no-MacBook-Air)30> 1 = getwild
1
iex(node_one@kodamasouichirou-no-MacBook-Air)31> 2 = getwild
** (MatchError) no match of right hand side value: 1
上記のコードの2行目について、getwildは1と1行目でマッチされているため、
左辺と右辺を入れ替えても式が成立します。
しかし、3行目は成立せず、MatchErrorが発生します。
(2 = 1 と同義)
ちなみに一度束縛した値を再束縛させないようにするには、ピン演算子 ^
を利用します。
iex(node_one@kodamasouichirou-no-MacBook-Air)55> getwild = 1
1
iex(node_one@kodamasouichirou-no-MacBook-Air)56> getwild = 2
2
iex(node_one@kodamasouichirou-no-MacBook-Air)57> ^getwild = 1
** (MatchError) no match of right hand side value: 1
iex(node_one@kodamasouichirou-no-MacBook-Air)57> ^getwild = 2
2
iex(node_one@kodamasouichirou-no-MacBook-Air)58> getwild
2
リスト
リストの構造は 空である
もしくは 1つのヘッドと1つのテイルからなる
そうです。ヘッドは1つの値を持ち、テイルはリストとなります。
よくわからん・・・。
コードを動かしてみましょう。
iex(node_one@kodamasouichirou-no-MacBook-Air)63> [k,d,m] = [1,2,3]
[1, 2, 3]
iex(node_one@kodamasouichirou-no-MacBook-Air)64> k
1
iex(node_one@kodamasouichirou-no-MacBook-Air)65> d
2
iex(node_one@kodamasouichirou-no-MacBook-Air)66> m
3
iex(node_one@kodamasouichirou-no-MacBook-Air)67>
上記の場合は、ただ単純に左辺の k,d,m
それぞれに右辺のリストの値がマッチされた状態です。
それでは、以下の場合はどうでしょうか。
iex(node_one@kodamasouichirou-no-MacBook-Air)71> [ get | wild ] = [1,2,3]
[1, 2, 3]
iex(node_one@kodamasouichirou-no-MacBook-Air)72> get
1
iex(node_one@kodamasouichirou-no-MacBook-Air)73> wild
[2, 3]
iex(node_one@kodamasouichirou-no-MacBook-Air)74>
お、なんか変な感じ・・・。
上記の場合、ヘッドにあたるのは
- 左辺はget
- 右辺は1
です。
テイルにあたるのは
- 左辺はwild
- 右辺は[2,3]
です。
たしかにヘッドは1つの値(上記だと1)を持ち、テイルはリスト(上記だと[2,3])になっている・・・。
再帰
Elixirではループ処理は再帰で表現をするそうです。
例えばリストの値を全て二乗する処理は
defmodule MyModule do
def square([]), do: []
def square([ head | tail ]), do: [ head * head | square(tail)]
end
こんな感じのモジュールを定義します。3行目が再帰になってます。
実際に動かしてみると
iex(node_one@kodamasouichirou-no-MacBook-Air)87> defmodule MyModule do
...(node_one@kodamasouichirou-no-MacBook-Air)87> def square([]), do: []
...(node_one@kodamasouichirou-no-MacBook-Air)87> def square([ head | tail ]), do: [ head * head | square(tail)]
...(node_one@kodamasouichirou-no-MacBook-Air)87> end
warning: redefining module MyModule (current version defined in memory)
iex:87
[1, 4, 9, 16, 25]
iex(node_one@kodamasouichirou-no-MacBook-Air)90>
リストの値が二乗されてます。
※warningは気にしないでください。同じモジュールが再定義されたことに対する警告です。
ちなみにMyModule内でsquareが2つ宣言されていますが、
これは引数が空のリストの場合と値が入っているリストの場合のパターンマッチを定義しています。
つまり、様々な引数のパターンに対応するために、同じ関数を複数定義しないといけないようです。
個人的には見やすくていいね、と、思いますが、そこは人それぞれな気がします・・・。
いろんなパターンを定義すると
iex(node_one@kodamasouichirou-no-MacBook-Air)113> defmodule MyModule do
...(node_one@kodamasouichirou-no-MacBook-Air)113> def square , do: IO.puts "No params"
...(node_one@kodamasouichirou-no-MacBook-Air)113>
...(node_one@kodamasouichirou-no-MacBook-Air)113> def square([]), do: []
...(node_one@kodamasouichirou-no-MacBook-Air)113> def square([ head | tail ]), do: [ head * head | square(tail)]
...(node_one@kodamasouichirou-no-MacBook-Air)113> def square(_), do: IO.puts "Get Wild"
...(node_one@kodamasouichirou-no-MacBook-Air)113> end
iex(node_one@kodamasouichirou-no-MacBook-Air)114> MyModule.square([1,2,3,4,5])
[1, 4, 9, 16, 25]
iex(node_one@kodamasouichirou-no-MacBook-Air)115> MyModule.square([])
[]
iex(node_one@kodamasouichirou-no-MacBook-Air)116> MyModule.square()
No params
:ok
iex(node_one@kodamasouichirou-no-MacBook-Air)117> MyModule.square(111)
Get Wild
:ok
iex(node_one@kodamasouichirou-no-MacBook-Air)118>
こんな感じになります。
先ほどまでの例に加えて、引数がない場合などを追加してみました。
パイプ演算子
Unixコマンドでよくある、コマンド同士を |
(パイプ)で繋ぐ処理ですが、Elixirにも似た機能が用意されています。
パイプ演算子 |>
というやつです。
iex(6)> defmodule MyModule do
...(6)> def myget do
...(6)> "Get"
...(6)> end
...(6)>
...(6)> def mywild(str) do
...(6)> "#{str}Wild"
...(6)> end
...(6)>
...(6)> def mygetwild do
...(6)> myget
...(6)> |> mywild
...(6)> end
...(6)> end
こんなモジュールがあるとします。
注目すべきは mygetwild
関数です。パイプ演算子を内部で利用しています。
コードを動かしてみるとこんな挙動です。
iex(7)> MyModule.myget
"Get"
iex(8)> MyModule.mywild ""
"Wild"
iex(9)> MyModule.mygetwild
"GetWild"
iex(10)>
関数の戻り値を次の処理に繋いでいます。Rubyだとメソッドチェインのような印象を受けました(見た目)。
個人的に思ったのが |>
が単純にカッコイイ・・・モテそう・・・
おわり
とりあえずこんな感じで、最近はElixirと遊んでいます。
Elixirの大きな特徴でもあるメタプログラミング(マクロ)やOTP、Erlangプロセスなど、
このあたりはまだ全然いじれていないので、次回遊んでみた結果を記事にでもしようと思います。