この記事は「Elixir Advent Calendar 2018」の18日目です!
昨日の記事は @takasehideki さんの「ElixirでIoT#3.1:ESP32やSTM32でElixirが動く!AtomVMという選択肢」でした。
今年は何か記事を書こうかなと思いElixirを選択しましたが、みなさん内容の濃い記事を書かれていて何を書くか凄く悩みましたが、今回はMap
について書こうと思います。初めてElixirを使う方たちに参考になればと思います!
Mapを処理する
今回は、僕がElixirを使い始めて一番最初につまずいた、Mapの使用方法、処理方法をいくつか紹介したいと思います。どれを使用するのが良いのかは、使用するデータに応じて変えるのが良いと思います。
まず、この様なMap用意します。
map = %{message: "Hello World", user: "example"}
方法1:パターンマッチング
# 正確なkeyの場合
%{message: message, user: user} = map
IO.puts message
> "Hello World"
IO.puts user
> "example"
# 無効なkeyの場合
%{hello: hello} = map
> ** (MatchError) no match of right hand side value
これは関数の引数を取得する時によく使いますね。
def print_message(%{message: message}) do
IO.puts message
end
print_message(map)
> "Hello World"
方法2:map.keyで取得(名前は知りません、誰か教えてください!)
# 正確なkeyの場合
IO.puts map.message
> "Hello World"
# 無効なkeyの場合
IO.puts map.hello
> ** (KeyError) key :hello not found in: %{message: "Hello World", user: "example"}.
方法3:map[key]で取得(名前知りません。)
# 正確なkeyの場合
IO.puts map[:message]
> "Hello World"
# 無効なkeyの場合
IO.inspect map[:hello]
> nil
方法4:get_in()
、Map.get
で取得
階層が深く無い時、入れ子になってない時は、Map.get
もしくは、Map.fetch
を使うのが良いと思います!
# 正確なkeyの場合
IO.puts get_in(map, [:message])
> "Hello World"
IO.puts Map.get(map, :message)
> "Hello World"
# 無効なkeyの場合
IO.inspect get_in(map, [:hello])
> nil
IO.puts Map.get(map, :hello)
> nil
どの方法をどのタイミングで使用するか。
ここからは、僕が個人的にこのような時にこの関数を使うと言うことを紹介していきたいと思います!
1: JSON APIを作成する時の、Controllerの関数。
皆さん、この様なコードを良く見ると思います。
def show(conn, %{"id" => id}) do
user = Accounts.get_user_by_otaku_id!(id)
render(conn, "show.json", user: user)
end
ここはパラメーターにid
があること前提で走らせる処理が書かれています。そこに、${"apple" => "pie"}
という値が来たらどうなるでしょうか? MatchError
になります。ControllerでMatchError
になるとActionClauseError
を生成します。また、PhoenixはActionClauseError
が生成されるとステータスコード400
を返す様な仕組みになっています。エンドポイントに対して不正なデータを送っているため、ステータスコード400
と言うのは正解です。
もちろん、間違えやすいエンドポイントだった場合パラメーターにid
が有るかどうか確認して、手動で分かりやすいエラー文と共にステータスを返す様な仕様にしても良いですが、怠け者の私はそんな事はしません。
2: 値の有無を確認したい時。
あたいの有無を確認したい時、例えばパースしたJsonデータにBirthday
と言うデータが入っているか確認したい時などはパターンマッチングで値を取得してしまうとその値がなかった時にMatchError
で処理が止まってしまいます。なので、そんな時はmap[key]
もしくはMap.fetch
などを使用してnilチェックを行うのが良いと思います。
階層の深いMapを処理する
古〜〜〜〜〜いAPIを叩いた時に、Schemaと違うじゃん!!みたいなことありますでしょうか?例え古くなくても、クローラーが走ってるAPIなどでは返ってくるデータが必ずしも一定じゃない事は良くあります。しかも、階層が何層にも深くなってて1つの値の配列が欲しいのにどう処理すれば良いのか分からなくなることがあります。そんな時に、この方法を使えば良いのでは無いのかと言うのを紹介します!いや、こう言う簡単な方法が有るじゃん。と言う方教えてくださると助かります。
まず、モックのデータです。
map = %{
"description": "これは説明。",
"properties": %{
"access_token": %{...},
"authenticated_user": %{...},
"comment": %{...},
"item": %{
"description": "アイテム",
"links": [
%{
"type": "リンク種類",
"description": "リンクの説明",
"href": "リンク",
"method": "GET",
"rel": "リファレンス",
"title": "タイトル"
}
]
}
}
}
access_token
のデータが欲しい
# access_tokenが有る場合
get_in(map, ["properties", "access_token"])
> %{...}
# access_tokenが無い場合
get_in(map, ["properties", "access_token"])
> nil
links
のtype
がa
の物だけ欲しい。
get_in
の引数にある配列には、key以外に、
- :get
- アクセスするデータ
- この関数の次に実行される関数
の3つを引数とした関数も渡すことができます。例としてlinks
のtype
がa
の物だけ欲しい場合
# データをEnum.filterでtypeがaの物だけにfilterしています。
# また、filterされた配列の要素全てに対して、渡された次の関数を実行しています。
justA = fn :get, data, next ->
data
|> Enum.filter(fn x -> x["type"] == "a" end)
|> Enum.map(next)
end
get_in(map, ["properties", "item", "links", justA])
>[
%{
"type": "a",
...
},
%{
"type": "a",
...
}
]
モックデータにはtype
がa
の物はありませんでしたが、この様な配列が出力されるはずです。
また、配列の最後に"href"
と入れると、type
がa
のhref
だけを取得できます。
get_in(map, ["properties", "item", "links", justA, "href"])
>[ "href", "href", "href" ]
データが不親切だと言うのはスルーしてください笑 Jsonデータ以外でも使用できるのでぜひ使ってみてください!
最後に
今回はMap
の処理方法をいくつか紹介しました!知っている方にとっては面白く無い内容だったかもしれないですし、たくさんElixirのコードを触っているわけでも無いので、間違っている部分やもっと簡単にできる部分があったかもしれません、その場合はぜひコメントで教えてください!すごく助かります!
明日の「Elixir Advent Calendar 2018」は@ma2geさんです!