Elixir

Elixir: with/1でパターンマッチングを扱う

with/1を用いると、複数の句についてパターンマッチングさせることができます。公式ドキュメントの例と説明を参考に、かみくだいてご紹介します。


case/2でパターンマッチングを入れ子にする

たとえば、つぎのような処理を行いたいとします。

iex> opts = %{width: 10, height: 15}

%{height: 15, width: 10}
iex> {:ok, width} = Map.fetch(opts, :width)
{:ok, 10}
iex> {:ok, height} = Map.fetch(opts, :height)
{:ok, 15}
iex> {:ok, width * height}
{:ok, 150}

この場合、パターンが一致しなければMatchErrorになってしまいます。

iex> opts = %{width: 10}

%{width: 10}
iex> Map.fetch(opts, :height)
:error
iex> {:ok, height} = Map.fetch(opts, :height)
** (MatchError) no match of right hand side value: :error

case/2を入れ子にしてパターンマッチングさせれば、エラーを切り分けることができるでしょう1

iex> opts = %{width: 10}

%{width: 10}
iex> case Map.fetch(opts, :width) do
...> {:ok, width} ->
...> case Map.fetch(opts, :height) do
...> {:ok, height} -> {:ok, width * height}
...> error -> error
...> end
...> error -> error
...> end
:error


複数の句をwith/1でパターンマッチングさせる

複数の句をパターンマッチングさせるのがwith/1です。<-の右辺が返す式の値を、左辺のパターンとマッチングさせます。そして、合致したらつぎの式に進み、合致しなかったときにはその戻り値をそのまま返すのです。入れ子が避けられて見やすくなります。

iex> opts = %{width: 10}

%{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
:error

すべての句が一致すれば、返されるのは最後の値です。

iex> opts = %{width: 10, height: 15, depth: 5}

%{depth: 5, height: 15, width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
{:ok, 150}


with/1の構文

with/1のパターンマッチングにガードを加えることもできます。

iex> opts = %{width: 10, height: "15"}

%{height: "15", width: 10}
iex> with {:ok, width} when is_number(width) <- Map.fetch(opts, :width),
...> {:ok, height} when is_number(height) <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
{:ok, "15"}

with/1の中の句で用いられる変数は、その外とは別個に扱われます。また、<-を使わない句も含めて構いません。

iex> with = nil

nil
iex> opts = %{width: 10, height: 15}
%{height: 15, width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, double_width * height}
...> end
{:ok, 300}
iex> with
nil

<-を用いない句の振る舞いは、with/1以外の構文と同じです。

iex> with :width <- :height, do: :ok

:height
iex> with :width = :height, do: :ok
** (MatchError) no match of right hand side value: :height

関数のように、with/1の引数となる句をかっこでくくっても構いません。また、一致しなかった場合の戻り値は、elseオプションで処理が加えられます。

iex> opts = %{width: 10}

%{width: 10}
iex> with(
...> {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height)
...> ) do
...> {:ok, width * height}
...> else
...> no_match -> {:error, no_match}
...> end
{:error, :error}

elseブロックでパターンマッチングに失敗すると、例外としてWithClauseErrorが発生します。

iex> opts = %{width: 10}

%{width: 10}
iex> with(
...> {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height)
...> ) do
...> {:ok, width * height}
...> else
...> {:error, no_match} -> {:error, no_match}
...> end
** (WithClauseError) no with clause matching: :error





  1. この例では、optsの値に対して、case/2で直接%{height: height, width: width} -> {:ok, height * width}のパターンマッチングを行えばもっと簡潔に書けます。実際には、はじめの句で得たデータから、さらにつぎの句でパターンマッチングするといった複数データを扱う場合に用いられます。公式ガイド「MIX AND OTP」の「with」で書かれているコードのserve/1がその例です。