Trace Helmsさんの2015年10月28日付のブログ記事Pattern Matching In Elixirの翻訳です。
Elixirではパターンマッチをうまく使うと条件判断などを使わないでプログラムが見通しよく綺麗に書けます。Idiomも多数あるようです。
入門記事としてよくまとまっていたので復習の意味も兼ねて翻訳してみました。
まえがき
新しい言語を学ぶことはプログラミングについての知識を広げるよい方法です。いくつかのかっこいい技や物事の新しい考え方を身に付けることができるでしょう。私が学んだ中でElixirが他とちょっと違うのはパターンマッチングでした。それはElixirのルーツであるErlangから来ており、私はこういう機能が他の言語でも使えたらなあとずっと思っているものです。
パターンマッチングとは何か
Elixirでは=
という記号は必ずしも「この変数の値を他の値と等しく設定する」という意味ではありません。その代わり「左辺を右辺とマッチさせる」という意味を持ちます。普通x = 2
のように宣言できるのでその違いには気づかないでしょうが、遅かれ早かれちょっとこれは違うな?と気づくことになるでしょう。
iex> x = 2
2
iex> y = 3
3
iex> 2 = x
2 # なんじゃこりゃ?
iex> 2 = y
** (MatchError) no match of right hand side value: 3
あれれ、2
はx
という値と等しく設定されてないですよね。2
はx
とパターンマッチされただけだったんです。この違いは現時点では大した違いに見えないでしょうが、いろいろと使いみちがあるんですよ。説明を続けさせてください。
イミュータブルな変数についての注記
Elixirは関数型言語であり、つまり変数はその値を変更されることがありません。しかし変数名は再利用できます。もしx =2
と設定して次にx = 3
と設定してもElixirでは問題ありません。
iex> x = 2
2
iex> y = 3
3
iex> x = y
3
iex> x
3
iex> ^x = 2 # ピン演算子で強制的に変数の再利用をしないようにした
** (MatchError) no match of right hand side value: 2
この状況の裏で、まだ変数x
で値が2
のままのものが残っています。例えば、並行動作する他の関数に変数x
を渡した後にx
を変更しても変更動作しているその関数は元のx
の値を持ったままです。^
1 を使うと強制的に変数の再利用をしないようにできますが、^x = 3
はx
がまさに値3
を持っているのでマッチします。
パターンマッチングの基礎
タプルがあったとして、その異なる値を幾つかの変数に割り当てたいとします。
iex> {result, value} = {:ok, 2}
{:ok, 2}
iex> result
:ok
iex> value
2
いいですねー。異なる値を取り出し変数に割り当てられました。ではここで結果が:ok
の時だけvalue
に値を割り当てたいときは?
iex> {:ok, value} = {:ok, 2}
{:ok, 2} # this passed the pattern match
iex> value
2
iex> {:ok, value} = {:nope, 2}
** (MatchError) no match of right hand side value: {:nope, 2}
左辺の:ok
が右辺と実際にマッチされています。value
はタプル全体(:ok
を含む)がマッチしたので値が割り当てられただけにすぎません。
2番めの式は:ok
が:nope
と異なる値のためマッチしません。またまたこれでは全然役に立たないみたいですが実際のコードでどのように使うか見てみましょう。2
# my_case.exs
defmodule MyCase do
def do_something(tuple) do
case tuple do
{:ok, value} ->
"The status was :ok!"
{:nope, value} ->
"Nope nope nope nope..."
_ ->
"You passed in something else."
end
end
end
ではこれを$ iex my_case.exs
でiex
にロードしてみます。
iex> MyCase.do_something({:ok, true})
"The status was :ok!"
iex> MyCase.do_something({:nope, true})
"Nope nope nope nope..."
iex> MyCase.do_something({:wat, true})
"You passed in something else."
これでパターンマッチングがElixirでは便利な道具であるとおわかりいただけるようになってくれればいいのですが。パターンマッチングを適切に使えばコードを読みやすくすることできます。
関数定義におけるパターンマッチング
ここがパターンマッチングを使うと本当に光るところで、その後もずっとパターンマッチングを使う気になると思います。引数がパターンとマッチした場合のみ実行される関数を定義できます。
# talker.exs
defmodule Talker
def say_hello(:bob), do: "Hello, Bob!"
def say_hello(:jane), do: "Hi there, Jane!"
def say_hello(name) do # name is a variable that gets assigned
"Whatever, #{name}."
end
end
ではこれを$ iex talker.exs
でiex
にロードしてみます。
iex> Talker.say_hello(:jane)
"Hi there, Jane!"
iex> Talker.say_hello(:bob)
"Hello, Bob!"
iex> Talker.say_hello("Trace")
"Whatever, Trace."
ご覧のとおり、3つの異なるメソッドが渡されたものによって呼び分けられていることがわかります。これがもっとすごいことができるための基礎となっています。例えばElixirでの再帰ですが、パターンマッチングに大いに依存しています。見てみましょう。
再帰での利用
リスト内の全ての変数を再帰的に2乗する関数を定義します。基本のケースとして空のリスト[]
を渡した場合の定義から始めます。そしてその後実際に2乗する関数を定義しましょう。
# my_squarer.exs
defmodule MySquarer do
def square([]), do: []
def square([head | tail]) do
[head * head | square(tail) ]
end
end
Elixirではリストを最初の要素(head)とそれ以降のリストの残りと表現することができます。最後の要素は常に空のリストです。ですので[head|tail]
と書けば文字通り最初の要素をhead
に、残りをtail
にマッチさせることができます。
# example showing what head and tail are
iex> [head | tail] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex> head
1
iex> tail
[2, 3, 4]
iex> [head | tail] = [1]
[1]
iex> head
1
iex> tail
[]
では我らのsquare/1
3関数に戻りましょう。この関数はhead
の2乗を先頭要素として持つ新しいリストを返し、残りの要素を埋めるべくsquare/1
を再帰的に呼び出します。最後の要素に到達した後はsquare([])
で定義された関数が空のリストとマッチして空のリストを返します。その時点で関数呼び出しの連鎖は終了して結果が返されます。
では動作を見てみましょう。$ iex my_squarer.exsで
iex`にロードしてみます。
iex> MySquarer.square([])
[]
iex> MySquarer.square([1, 2, 3])
[1, 4, 9]
iex> MySquarer.square([-1, -2, -3])
[1, 4, 9]
まとめ
Elixirでパターンマッチングがどのような意味を持ちどれほど使えるものかご理解いただければ幸いです。再帰呼び出しはトリッキーですがElixirのパターンマッチングを使うことで多少は読んで理解するのが簡単になります。