パターンマッチと制御構文
- このページの目標
- パターンマッチの出発点に立つ
- 頻出の制御構文(
case
)に触れ、読めるようになる
- 所要時間: 20 分程度
パターンマッチの基礎
- Elixir はパターンマッチと呼ばれる構文が強力です
- 機能の多寡はあれど、多くの言語に類似機能が存在します
- 手続き型言語で一般的な「変数」と「代入」に似ていますが、考え方が少し異なり、またより柔軟です
- 他言語から移ってくると混乱をきたしやすい部分でもありますので、ここで説明しておきます
- 例えば、以下のようなリストがあります
iex> l = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
=
がでてきましたが、そもそも Elixir では=
はマッチ演算子です。「代入」演算子ではありません- ここでは、まず左辺の
l
という変数にはいかなる条件も存在しない(いわば自由変数である)ので、 右辺のリストをまるごと「受け取る」ようなかたちで「マッチ」します- 自由な変数は、どんな形にも変形できる器のようなものです。対象とする値に合わせて形を変え、変わった形を維持しようとします。
- 結果として、
l
には右辺のリストが代入されたような格好になります- つまり
l
は右辺のリストで束縛; bindされました。意味(値)を持ち、「自由」ではなくなったのです
- つまり
- その証拠に、こんなことをしてみましょう
iex> [1, 2, 3, 4, 5] = m
** (CompileError) iex:1: undefined function m/0
m
は新しい変数ですが、どこにも定義されていません。Elixir はこれをm/0
という関数と解釈しようとしましたが、当然見つからないのでエラーです- 一方、
iex> [1, 2, 3, 4, 5] = l
[1, 2, 3, 4, 5]
l
であればこの式は成り立ちます!l
はすでにリストで束縛されており、かつ、ここでの左辺と内容が全く同じだからです- これで、Elixir の
=
が「代入」ではなくあくまで「マッチ演算子」であることがはっきりしました
- マッチ演算子の左辺では、様々な「条件」を表現できます
- すでに、「全く無条件」(新しい変数の導入)と、「(何らかのリストのような、)固定の値」という 2 パターンが登場しました
- たとえば、「空でないリスト」のような条件もあります
iex> [head | tail] = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> head
1
iex> tail
[2, 3, 4, 5]
[head | tail]
という構文は、空でないリストにマッチし、head
を先頭の値tail
を先頭をのぞいたリスト
- でそれぞれ束縛します
- 実は、このように「先頭と、それ以降」という形でのパターンマッチが可能なのは、Elixir のリストがLinked Listだからです。 Linked list は「値と、次の値への参照」というデータのチェーンで表現されるリストの一種で、先頭へのアクセス(読み出し・追加)が高速なデータ構造です
- 関数型言語では、配列; Array ではなく Linked list を主たるデータ構造として利用することが多いです
- 空のリストを与えれば、
iex> [head | tail] = []
** (MatchError) no match of right hand side value: []
- エラーとなります
- ほかにもパターンマッチで利用できる条件・束縛の表記はたくさんありますので、実際にコードを書く中で徐々に慣れていってください
- 特に pin operator を使ったパターンマッチは、ちょっと難しいですがたまに使われるものなので、あとからでも学んでおきましょう。資料リンク先に説明があります
case
式
資料: case, cond, and if - Elixir
- パターンマッチはマッチ演算子
=
以外にも、case
式- 関数の引数部分
- で行うことができます。例えば以下のようなパターンマッチは頻出です
case some_list do
[] ->
"empty!"
[head | _] ->
"first value: " <> to_string(head)
end
fn
[] ->
"empty!"
[head | _] ->
"first value: " <> to_string(head)
end
- パターンマッチで、マッチした値を変数に束縛しない場合は
_
で始まるプレースホルダ変数が使えます - 見て分かる通り、
case
式でのパターンマッチは すなわち条件分岐にほかなりません- Elixir では 真偽値; boolean を条件とする場合には
if
を使いますが、 それ以外の値を対象に条件分岐する場合、case
が最も頻繁に登場します
- Elixir では 真偽値; boolean を条件とする場合には
- (例に登場している
to_string/1
は標準ライブラリが提供する文字列変換関数です)