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