使い方
二つの条件が成り立った場合に処理したいとき、普通に書くと二重のcase文になります。
def area(opts) do
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
end
これを纏めて書けるのがwith式でした。
def area(opts) do
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
end
end
使いたい時あるかも。
アンチパターン
with式はelse分を使ってエラーになった時の値を書き換える事ができます。
def open_decoded_file(path) do
with {:ok, encoded} <- File.read(path),
{:ok, decoded} <- Base.decode64(encoded) do
{:ok, String.trim(decoded)}
else
{:error, _} -> {:error, :badfile}
:error -> {:error, :badencoding}
end
end
{:error, _} を返した場合は、{:error, :badfile}を返す
:errorを返した場合は、{:error, :badencoding}を返す
File.readは、エラーの場合、{:error, _}を返すので、:badfile
Base.decode64(encoded) はエラーの場合、:errorを返すので、:badencoding
おお。これは、ちょっと気持ち悪い。
エラーが発生した場所と、戻り値の対応が力業な記述になってます。
一見しただけでは、動作が読み取れません。
将来的に、withの中に別の処理を加えたり、返値の型が変わったら大変な事になります。
次のように書くのがおすすめです。
def open_decoded_file(path) do
with {:ok, encoded} <- file_read(path),
{:ok, decoded} <- base_decode64(encoded) do
{:ok, String.trim(decoded)}
end
end
defp file_read(path) do
case File.read(path) do
{:ok, contents} -> {:ok, contents}
{:error, _} -> {:error, :badfile}
end
end
defp base_decode64(contents) do
case Base.decode64(contents) do
{:ok, decoded} -> {:ok, decoded}
:error -> {:error, :badencoding}
end
end
else文の使い道を間違えないようにしましょう。
説明のプログラムは、hexdocsの説明のプログラムから引用しました。
本家の説明
https://hexdocs.pm/elixir/1.16.0-rc.0/Kernel.SpecialForms.html#with/1
https://hexdocs.pm/elixir/1.16.0-rc.0/code-anti-patterns.html#complex-else-clauses-in-with