LoginSignup
23
20

More than 5 years have passed since last update.

[翻訳] Elixirのパターンマッチング

Posted at

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

あれれ、2xという値と等しく設定されてないですよね。2xとパターンマッチされただけだったんです。この違いは現時点では大した違いに見えないでしょうが、いろいろと使いみちがあるんですよ。説明を続けさせてください。

イミュータブルな変数についての注記

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 = 3xがまさに値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.exsiexにロードしてみます。

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.exsiexにロードしてみます。

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/13関数に戻りましょう。この関数はheadの2乗を先頭要素として持つ新しいリストを返し、残りの要素を埋めるべくsquare/1を再帰的に呼び出します。最後の要素に到達した後はsquare([])で定義された関数が空のリストとマッチして空のリストを返します。その時点で関数呼び出しの連鎖は終了して結果が返されます。
では動作を見てみましょう。$ iex my_squarer.exsiex`にロードしてみます。

iex> MySquarer.square([])
[]
iex> MySquarer.square([1, 2, 3])
[1, 4, 9]
iex> MySquarer.square([-1, -2, -3])
[1, 4, 9]

まとめ

Elixirでパターンマッチングがどのような意味を持ちどれほど使えるものかご理解いただければ幸いです。再帰呼び出しはトリッキーですがElixirのパターンマッチングを使うことで多少は読んで理解するのが簡単になります。


  1. pin operator(ピン演算子) 

  2. 念のため、_は「どんなものともマッチする」変数です。ワイルドカードってElixirでは正式名称でしたっけ?(Scalaだとワイルドカードで通じる) 

  3. square/1という書き方はElixir/Erlangの表現方法で「引数がひとつ(=アリティが1)なsquareという関数」を意味します。 

23
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
20