withwithという特殊形式を使うと、失敗する可能性のある一連のステップのうち「ハッピーパス」に集中し、失敗への対処は後回しにできます。
with {:ok, id} <- get_id(username),
{:ok, avatar} <- fetch_avatar(id),
{:ok, image_type} <- check_valid_image_type(avatar) do
{:ok, image_type, avatar}
else
:not_found ->
{:error, "invalid username"}
{:error, "not an image"} ->
{:error, "avatar associated to #{username} is not an image"}
err ->
err
end
各ステップでは、節がマッチすれば、doブロックが実行されるまで連鎖が続きます。マッチに失敗すると、連鎖はそこで止まり、マッチしなかった節が返されます。elseブロックを使えば、失敗したマッチを捕捉し、戻り値を変更することもできます。
パスポートの有効期限がもうすぐ切れるので、更新のために市役所に立ち寄る必要があります。これまでの経験から、自分の市役所が必ずしも対応しやすいわけではないと分かっているので、常に「ハッピーパスに集中する」よう最善を尽くすことにします。
新しいパスポートを受け取るのに必要な申請書を印刷して記入し、車に飛び乗ってブロックを一周し、駐車して役所へ向かいます。
以下のタスクはすべて、get_new_passport/3を実装して拡張していく必要があります。
実は、この建物は午後しか開いておらず、しかも毎日同じ時間に開いているわけではありません。
現在時刻(get_new_passport/3の第1引数として渡されます)を指定して、enter_building/1を呼び出します。建物が開いていれば、この関数は:okと、後で必要になるタイムスタンプのタプルを返し、そうでなければ:errorとメッセージのタプルを返します。とりあえず、ハッピーパスでは:okのタプルを返すようにします。
:errorのタプルが返ってきた場合は、elseブロックでそれを返します。
案内所は長いコーヒーブレイクで有名です。運よく誰かを見つけられれば、生年月日に応じてどの窓口に行けばよいかを説明してくれる説明書を渡してくれます。
現在時刻を指定して、find_counter_information/1を呼び出します。:okと、無名関数で表される説明書のタプルが返るか、:coffee_breakとさらなる指示のタプルが返るかのどちらかです。説明書を受け取るハッピーパスでは、それを誕生日(get_new_passport/3の第2引数)に適用します。行く必要のある窓口の番号が返るので、その窓口番号を含む:okのタプルを返します。
:coffee_breakのメッセージが返ってきた場合は、:retryと、現在時刻の15分後を指すNaiveDateTimeのタプルを返します。前と同様、:errorのタプルが返ってきた場合はそれを返します。
どういうわけか、窓口によって必要な申請書の色が異なります。もちろん、ウェブサイトで最初に見つけたものを印刷したので、ハッピーパスに集中して、うまくいくことを願うばかりです。
入り口で受け取ったタイムスタンプ、窓口、持ってきた申請書(get_new_passport/3の最後の引数)を指定して、stamp_form/3を呼び出します。:okと、パスポート番号の検証に使われるチェックサムのタプルが返るか、:errorとメッセージのタプルが返るかのどちらかです。ハッピーパスでは、チェックサムを含む:okのタプルを返すようにします。:errorのタプルが返ってきた場合は、それを返します。
ついに、必要な書類がすべてそろいました。
先ほど受け取ったタイムスタンプ、窓口、チェックサムを指定して、get_new_passport_number/3を呼び出します。最終的なパスポート番号の文字列が返ってくるので、あとはその文字列を:okのタプルで返して家に帰るだけです。