LoginSignup
13
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-18

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がその例です。 

13
3
2

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