この記事では私が個人的にElixirを学んで感動した言語仕様などを紹介するものです。これを見て、Elixirを学ぼうとする方が増えると嬉しいです。
ちなみに、Elixirの特徴である「並列性/並行性」は今の私では理解が曖昧なので、この記事の範囲から外しています。
では、早速本題に入りたいと思います。
パターンマッチ
これがElixirの特徴で最も代表的で強力な概念だと感じています。それ故に難しいとも感じました。
この記事は結構パターンマッチについて説明がなされていて、良いなぁと思いました。ちょっと古いですが。
Elixir のパターンマッチを攻略しよう
Elixirでは=のことをマッチ演算子と言います。代入演算子ではありません。注意しましょう。
具体的なコードを見てみましょう。
iex> number = 1
1
iex> 1 = number
1
iex> 2 = number
** (MatchError) no match of right hand side value: 1
一行目はnumberという変数に1を代入しています。、、、と思ってしまいそうですが、違います。
これはnumberという変数に1という値を束縛させているのです。要するに左辺の値に右辺の値を束縛させるのがマッチ演算子が行っていることです。
マッチ演算子は束縛に成功すると束縛に成功した値を返します。
iex> string = "hogefugabazzfizz"
"hogefugabazzfizz"
iex> "hogefugabazzfizz" = string
"hogefugabazzfizz"
この例ではstringという変数に"hogefugabazzfizz"
という文字列をstring
という変数に束縛することに成功しています。なので、"hogefugabazzfizz"
という文字列が返ってきます。
束縛に失敗すると、以下のような例外が発生します。
iex> "koheiyamaguchi" = string
** (MatchError) no match of right hand side value: "hogefugabazzfizz"
string変数は"hogefugabazzfizz"
という文字列が束縛されています。よって、"koheiyamaguchi"
という文字列に対して束縛を行おうとしても例外になっていまいます。
と書いたものの、そういう仕様だから、そういうものだと思うしかないと思っています。ちょっと完璧に理解できている気がしないですが、ひとまず良しとしました。
変数に対するパターンマッチを紹介しましたが、Elixirでは他にも多くのところでパターンマッチが使われています。
関数定義です。もちろん、他にもありますが、ここで説明するのはこの1つの例にします。
関数定義をする際にこのように書くことができます。
defmodule PatternMatch do
def explain(1) do
"1とは1(一、いち、ひと、ひとつ)は、最小の正の整数である"
end
#nが2の場合この関数が実行される。
def explain(2) do
"2(二、に、じ、ふた、ふたつ)は自然数、また整数において、1 の次で 3 の前の数である。"
end
#nが3の場合この関数が実行される。
def explain(3) do
"3(三、さん、み、みっつ、みつ)は自然数、また整数において、2 の次で 4 の前の数である。"
end
#nが1~3以外の場合この関数が実行される。
def explain(n) do
"#{n}は1~3の値のどれでもない。1~3のいずれかの値を引数に渡してください。"
end
end
iex> PatternMatch.explain 1
"1とは1(一、いち、ひと、ひとつ)は、最小の正の整数である"
iex> PatternMatch.explain 2
"2(二、に、じ、ふた、ふたつ)は自然数、また整数において、1 の次で 3 の前の数である。"
iex> PatternMatch.explain 3
"3(三、さん、み、みっつ、みつ)は自然数、また整数において、2 の次で 4 の前の数である。"
iex> PatternMatch.explain 4
"4は1~3の値のどれでもない。1~3のいずれかの値を引数に渡してください。"
これはexplain/1という関数に渡される引数と関数定義の際に渡されている値とでパターンマッチを行います。
PatternMatch.explain 1
は1を引数として渡されています。関数定義を見てみると、
def explain(1) do
"1とは1(一、いち、ひと、ひとつ)は、最小の正の整数である"
end
とあります。この関数の引数のところに1とあります。この1に引数として渡した1がマッチしているので、この関数が呼ばれます。
Elixirはパターンマッチが到るところで使われています。このパターンマッチの挙動を知り、使いこなすのがElixirで開発する上でとても大切になると思われますね。
パイプライン演算子
Elixirに入門してない人に絶対に聞かれるものの一つがパイプライン演算子です。
|>
これを用いた具体的なコードはこちらです。
iex> [3,90,33] |> Enum.sort(fn(n1,n2) -> n1 >= n2 end)
パイプライン演算子の左辺にある値[3,90,33]
を右辺にあるsort/2
関数の第一引数に渡して、その関数を実行しています。
これは関数を2つ以上連続して呼び出したいときに使うことが想像できます。どういうことなのか、具体的なコードを下に示します。
iex> [3,90,33,55,78]
iex> |> Enum.sort(fn(n1,n2) -> n1 >= n2 end)
iex> |> Enum.map(fn(n) -> n * 2 end)
iex> |> Enum.join(",")
"180,156,110,66,6"
もしElixirにパイプライン演算子がなかったら、こうなります。
iex> Enum.join(Enum.map(Enum.sort([3,90,33,55,78], fn(n1,n2) -> n1 >= n2 end), fn(n) -> n * 2 end), ",")
"180,156,110,66,6"
とても読みづらくないでしょうか?私はパイプライン演算子がElixirに用意されていなかったら、Elixirは絶対に勉強を続けられなかったでしょう。
ガード節
Elixirは関数型言語です。多くの小さな関数を定義してプログラムを組みます。その関数を定義する際に、「ガード節」というプログラミング手法があります。
ガード節とは関数に付属するもので、そこに渡された式が真を帰す場合のみ、その関数を実行するというものです。
以下に具体例を示します。
defmodule Guard do
#nが1の場合この関数が実行される。
def explain(n) when n == 1 do
"1とは1(一、いち、ひと、ひとつ)は、最小の正の整数である"
end
#nが2の場合この関数が実行される。
def explain(n) when n == 2 do
"2(二、に、じ、ふた、ふたつ)は自然数、また整数において、1 の次で 3 の前の数である。"
end
#nが3の場合この関数が実行される。
def explain(n) when n == 3 do
"3(三、さん、み、みっつ、みつ)は自然数、また整数において、2 の次で 4 の前の数である。"
end
#nが1~3以外の場合この関数が実行される。
def explain(n) do
"#{n}は1~3の値のどれでもない。1~3のいずれかの値を引数に渡してください。"
end
end
{:module, Guard,
<<70, 79, 82, 49, 0, 0, 7, 12, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 157,
0, 0, 0, 15, 12, 69, 108, 105, 120, 105, 114, 46, 71, 117, 97, 114, 100, 8,
95, 95, 105, 110, 102, 111, 95, 95, 9, ...>>, {:explain, 1}}
Guard.explain(1)
"1とは1(一、いち、ひと、ひとつ)は、最小の正の整数である"
Guard.explain(2)
"2(二、に、じ、ふた、ふたつ)は自然数、また整数において、1 の次で 3 の前の数である。"
Guard.explain(3)
"3(三、さん、み、みっつ、みつ)は自然数、また整数において、2 の次で 4 の前の数である。"
Guard.explain(100)
"100は1~3の値のどれでもない。1~3のいずれかの値を引数に渡してください。"
プログラムの具体例が悪いですが、これを見たときにElixirを使うと条件分岐がかなり減るのでは?と思いました。実際に「プログラミングElixir」のP121ページでは以下のような記述があります。
Elixirでは、たくさんの小さな関数を書く。そして、ガード節とパターンマッチの組み合わせが、他の言語にある、ほとんどの制御構文の置き換えになるのだ。
しかし、Elixirには、制御フローのちょっとしたセットがある。紹介するタイミングをここまで遅らせたのは、あまり多用しないでほしいからだ。確かにコードの中にifやcseを書くこともあるだろうし、書くべき場合もあるだろう。でも、それをする前に、もっと関数的な代案を考えてみよう。コードを書けば書くほど、その恩恵は明らかになっていく。
この本の著者であるDave Thomasは数々の著名な技術書を書いている人です。そんな人がこのようなことを言うのだから、僕の直感はある程度正しかったのでしょう。
追記
TwitterでElixirとツイートするとfukuoka.ex関係者から多くのリプライやイイネが来ます。Elixirコミュニティは数はまだ少ないが、とても温かいので、かなり助けられました。これもすごい良いことだと思います。