ElixirのMapについて。また、階層の深いMapを処理する方法。

この記事は[「Elixir Advent Calendar 2018」](https://qiita.com/advent-calendar/2018/elixir)の18日目です！
昨日の記事は @takasehideki さんの「[ElixirでIoT#3.1：ESP32やSTM32でElixirが動く！AtomVMという選択肢](https://qiita.com/takasehideki/items/847d3d8f9d255e01ce9c)」でした。

今年は何か記事を書こうかなと思いElixirを選択しましたが、みなさん内容の濃い記事を書かれていて何を書くか凄く悩みましたが、今回は`Map`について書こうと思います。初めてElixirを使う方たちに参考になればと思います！

# Mapを処理する

今回は、僕がElixirを使い始めて一番最初につまずいた、Mapの使用方法、処理方法をいくつか紹介したいと思います。どれを使用するのが良いのかは、使用するデータに応じて変えるのが良いと思います。

まず、この様なMap用意します。
`map = %{message: "Hello World", user: "example"}`

## 方法１：パターンマッチング

```Elixir
# 正確な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
```
これは関数の引数を取得する時によく使いますね。

```Elixir
def print_message(%{message: message}) do
    IO.puts message
end

print_message(map)
> "Hello World"
```

## 方法２：map.keyで取得（名前は知りません、誰か教えてください！）
```Elixir
# 正確な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]で取得（名前知りません。）
```Elixir
# 正確なkeyの場合
IO.puts map[:message]
> "Hello World"

# 無効なkeyの場合
IO.inspect map[:hello]
> nil
```

## 方法4:`get_in()`、`Map.get`で取得
階層が深く無い時、入れ子になってない時は、`Map.get`もしくは、`Map.fetch`を使うのが良いと思います！

```Elixir
# 正確な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
```

# どの方法をどのタイミングで使用するか。

ここからは、僕が個人的にこのような時にこの関数を使うと言うことを紹介していきたいと思います！

## １: JSON APIを作成する時の、Controllerの関数。

皆さん、この様なコードを良く見ると思います。

```Elixir
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などでは返ってくるデータが必ずしも一定じゃない事は良くあります。しかも、階層が何層にも深くなってて１つの値の配列が欲しいのにどう処理すれば良いのか分からなくなることがあります。そんな時に、この方法を使えば良いのでは無いのかと言うのを紹介します！いや、こう言う簡単な方法が有るじゃん。と言う方教えてくださると助かります。

まず、モックのデータです。

```Elixir
map = %{
    "description": "これは説明。",
    "properties": %{
    	"access_token": %{...},
    	"authenticated_user": %{...},
    	"comment": %{...},
    	"item": %{
    		"description": "アイテム",
    		"links": [
    			%{
    				"type": "リンク種類",
    				"description": "リンクの説明",
    				"href": "リンク",
    				"method": "GET",
    				"rel": "リファレンス",
    				"title": "タイトル"
    			}
    		]
    	}
    }
}
```

## `access_token`のデータが欲しい

```Elixir
# access_tokenが有る場合
get_in(map, ["properties", "access_token"])
> %{...}

# access_tokenが無い場合
get_in(map, ["properties", "access_token"])
> nil
```
## `links`の`type`が`a`の物だけ欲しい。
`get_in`の引数にある配列には、key以外に、

* :get
* アクセスするデータ
* この関数の次に実行される関数

の３つを引数とした関数も渡すことができます。例として`links`の`type`が`a`の物だけ欲しい場合

```Elixir
# データを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`だけを取得できます。

```Elixir
get_in(map, ["properties", "item", "links", justA, "href"])
>[ "href", "href", "href" ]
```
データが不親切だと言うのはスルーしてください笑 Jsonデータ以外でも使用できるのでぜひ使ってみてください！

# 最後に
今回は`Map`の処理方法をいくつか紹介しました！知っている方にとっては面白く無い内容だったかもしれないですし、たくさんElixirのコードを触っているわけでも無いので、間違っている部分やもっと簡単にできる部分があったかもしれません、その場合はぜひコメントで教えてください！すごく助かります！

明日の[「Elixir Advent Calendar 2018」](https://qiita.com/advent-calendar/2018/elixir)は@ma2geさんです！