Elixirのパターンマッチ
JSONを取得するサンプル
APIサーバー
http://localhost:3000/capitals.jsonにアクセスすると首都一覧を返します。
const express = require('express') const app = express() app.get('/capitals.json', (request, response) => { response.contentType('application/json') const capitals = [ { country: 'China', city: 'Beijing' }, { country: 'Japan', city: 'Tokyo' }, { country: 'Mongolia', city: 'Ulan Bator' }, { country: 'North Korea', city: 'Pyhongyang' }, { country: 'South Korea', city: 'Seoul' }, ] response.send(JSON.stringify(capitals)) }) app.listen(3000)
クライアント
HTTPoisonのサンプル通りに記述してJSONを取得します。
defmodule JsonHandler do def main(args) do fetch_capitals end def fetch_capitals do url = "http://localhost:3000/capitals.json" case HTTPoison.get(url) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> IO.puts body {:ok, %HTTPoison.Response{status_code: 404}} -> IO.puts "Not found :(" {:error, %HTTPoison.Error{reason: reason}} -> IO.puts "Unknown error" end end end
実行
$ ./json_handler [{"country":"China","city":"Beijing"},{"country":"Japan","city":"Tokyo"},{"country":"Mongolia","city":"Ulan Bator"},{"country":"North Korea","city":"Pyhongyang"},{"country":"South Korea","city":"Seoul"}]
最初のcaseにマッチするのでレスポンスが出力されました。
次にサーバーを起動しない状態で実行すると...
$ ./json_handler Unknown error
最後のcaseにマッチするのでメッセージが出力されます。
ではマッチしないケースはどうなるでしょうか。
503を返すようにサーバーを書き換えて実行してみます。
response.status(503)
response.send()
$ ./json_handler ** (CaseClauseError) no case clause matching: {:ok, %HTTPoison.Response{body: "", headers: [{"X-Powered-By", "Express"}, {"Content-Type", "application/json"}, {"Date", "Thu, 18 Aug 2016 04:39:06 GMT"}, {"Connection", "keep-alive"}, {"Content-Length", "0"}], status_code: 503}} (error_handling_sample) lib/error_handling_sample.ex:9: ErrorHandlingSample.fetch_capitals/0 (elixir) lib/kernel/cli.ex:76: anonymous fn/3 in Kernel.CLI.exec_fun/2
マッチするケースがないよ、ってことでクラッシュしてしまいます。
というわけで200, 404以外にマッチするパターンを追加します。
case HTTPoison.get(url) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> IO.puts body {:ok, %HTTPoison.Response{status_code: 404}} -> IO.puts "Not found :(" {:ok, %HTTPoison.Response{status_code: status_code}} -> IO.puts "Status code #{status_code}" {:error, %HTTPoison.Error{reason: reason}} -> IO.puts "Unknown error" end
これで503も拾えました。
$ ./json_handler
Status code 503
case文を関数化する
もう少しElixirらしく?、各パターンを関数に書き換えます。
要注意なのがcase文と同じく先にある関数からマッチがされていくという点です。
つまり関数の記述順序も気にしないといけません。(まぁこの場合はパターンが悪いと思いますが)
defmodule JsonHandler do def main(args) do fetch_capitals end def fetch_capitals do url = "http://localhost:3000/capitals.json" HTTPoison.get(url) |> process_response end defp process_response({:ok, %HTTPoison.Response{status_code: 200, body: body}}) do IO.puts body end defp process_response({:ok, %HTTPoison.Response{status_code: 404}}) do IO.puts "Not found :(" end defp process_response({:ok, %HTTPoison.Response{status_code: status_code}}) do IO.puts "Status code #{status_code}" end defp process_response({:error, %HTTPoison.Error{reason: reason}}) do IO.puts "Unknown error" end end
クラッシュ怖い
マッチするパターンがないとクラッシュするって言われると、あらゆるパターン(特にエラー処理)を想定しなければならず、正常系よりも異常系の処理を書くことがメインのお仕事になってきます。
そんなの辛い。
"Let it crash."
Elixir(とそのベースになっているErlang)のプロセスは生成のためのコストが小さいため「下手にエラー処理するコードを書いてプロセスを維持するよりはさっさとクラッシュさせて、それに続く処理の中で対策して再起動したほうがよい」という思想があります。
クラッシュ上等というわけですね。
というわけで今度はクラッシュしまくるスタイルに書き換えてみる予定です。