LoginSignup
7
7

More than 5 years have passed since last update.

[翻訳] Elixirノート:Mapのパターンマッチング

Posted at

Rob Phoenixさんの2016年1月4日付のブログ記事Notes on Elixir: Pattern-Matching Mapsの翻訳です。ここのところ立て続けにElixirについてのノートを投稿していて前回の投稿は私の前回の翻訳とほぼ同じ内容でした。つまり単純なスカラー型データやリストについての例です。
今回取り挙げられているのはMap型のデータでElixirのパターンマッチングがどうなるか、という例です。


パターンマッチングについての前回の投稿からの続きで、Elixirで主なキー・バリューストアとして使われるmap型データは、パターンマッチングについては他のデータ構造と全然違う面白い機能を持っています。

mapはその値のまさにサブセットだけをパターンマッチさせることができます。マッチングのパターン内のキーはマッチさせる対象の中に存在している必要がありますが、listtupleの場合のようにマッチング対象とパターンが正確に一致している必要はありません。例えば:

iex(1)> [a, b] = [1, 2, 3]
** (MatchError) no match of right hand side value: [1, 2, 3]

iex(1)> {a, b} = {1, 2, 3}
** (MatchError) no match of right hand side value: {1, 2, 3}

iex(1)> %{:a => one} = %{:a => 1, :b => 2, :c =>3}
%{a: 1, b: 2, c: 3}
iex(2)> one
1
iex(3)> %{:a => one, :c => three} = %{:a => 1, :b => 2, :c =>3}
%{a: 1, b: 2, c: 3}
iex(4)> three
3
iex(5)> one
1
iex(6)> %{} = %{:a => 1, :b => 2, :c =>3}
%{a: 1, b: 2, c: 3}
iex(7)> %{:d => four} = %{:a => 1, :b => 2, :c =>3}
** (MatchError) no match of right hand side value: %{a: 1, b: 2, c: 3}

iex(8)> %{:a => one, :d => four} = %{:a => 1, :b => 2, :c =>3}
** (MatchError) no match of right hand side value: %{a: 1, b: 2, c: 3}

listtupleもパターンのデータ構造とマッチしようとするデータ構造、端的には大きさ、が異なっているとマッチングに失敗するのがわかりますね1。ところがmapでは事情が異なります。キーがパターンと、マッチしようとするデータの両方に含まれているならば、それらの大きさが異なっていてもマッチングは成功します。空のmapであっても2です。
しかしながらパターンに入っているキーがマッチしようとするデータに入っていない場合はマッチングは失敗します。これはたとえ他のキーがマッチした場合であってもです。従ってパターンで使われる全てのキーはマッチしようとするデータに含まれている必要があります。

この仕組みはPhoenix Frameworkの中でパラメータを関数に引き渡そうとする際に広く使われています。関数は必要とするデータだけを取り出すことができるのです。

新規ユーザーを生成するための関数はこうなります:

def create(conn, %{"user" => user_params}) do
  # 変数user_paramを使って何かする
end

この関数の第2引数として渡されるmapの中身は実際にはこのようになっています:

Parameters: %{"_csrf_token" => "UR99GwILHTtzbSBUYRwmBVpdeDY/AAAA3K70jEiO9UhgPVwh+d3WYw==",
              "_utf8" => "✓",
              "user" => %{"name" => "Memphis Minnie",
                          "password" => "[FILTERED]",
                          "username" => "minnie"}}

create関数は"user"キーに対してのみパターンマッチングを行い、対応する値、この場合はまた別のmapですが、をこの関数内で使用される変数user_paramsにバインドします。この関数は基本的に与えられたパラメータのmapからユーザー情報だけを取り出し、必要としているものと関係ない残りのデータは捨ててしまいます。

map全体をマッチしつつ同時にパターンマッチされたmapの部分をマッチすることも可能です。create関数の中身を書き換えてみましょう:

def create(conn, parameters = %{"user" => user_params}) do
  IO.inspect user_params["name"]
  IO.inspect parameters["_utf8"]
  # 変数user_paramを使って何かする
end

この関数を使ってparameterと渡されたパラメータ全体のmapをマッチさせ、user_paramsをパラメータ全体mapの"user"キーとマッチさせることで以下のように表示されるはずです。

[info] POST /users
"Skip James"
"✓"

面白いことにparametersのパターンマッチングは他の書き方もできます:

def create(conn, %{"user" => user_params} = parameters) do
  IO.inspect user_params["name"]
  IO.inspect parameters["_utf8"]
  # 変数user_paramを使って何かする
end

なぜならここでは%{"user" => user_params} = parametersがマッチのパターンであり、マッチさせようとするデータが渡されたパラメータ全体のmapだからです。さらにパターンの内側で異なる部分をパターンとマッチさせて、他の変数にバインドすることもできます。私の知る限りこれはより慣用句的に使われている手法で、ほとんどの場合こちらが見られるでしょう。

パターンマッチングはmapキーバリューストアから値を取り出すのに実に優れた方法を提供してくれます。


  1. 上記の例の最初の2行のことです。 

  2. 上記の例のiex(6)のところです。 

7
7
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
7
7