プログラミング Elixir 第八章

# 概要

[プログラミング Elixir](https://www.amazon.co.jp/dp/4274219151)

パターンマッチを使ったマップの利用方法、構造体や辞書型の使い方を解説している

# マップとキーワードリストどちらを使うべきか

以下の条件で、[キーワードリスト](http://qiita.com/pekepek/items/4ab44e33bf3cf5cba4b2#%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%83%AA%E3%82%B9%E3%83%88) `[a: 1, b: 2, c: 3]` と[マップ](http://qiita.com/pekepek/items/4ab44e33bf3cf5cba4b2#%E3%83%9E%E3%83%83%E3%83%97) `%{a: 1, b: 2, c: 3}` をどうやって使い分ける

キーワードリスト

* 同じキーが 2 回以上現れる 
* 要素の順番を保証する必要がある

マップ

* 内容について、パターンマッチを行う必要がある
* その他諸々

# キーワードリスト

主に関数に渡されるオプションの用途で使われる。

```ex
defmodule Database do
  @defaults [
    adapter: "sqlite3",
    encoding: "utf8",
    pool: 5,
    password: "hoge",
    socket: "/tmp/mysql.sock"
  ]

  def configures(env, options \\ []) do
    options = Keyword.merge(@defaults, options)

    IO.puts "#{env}:"
    IO.puts "  adapter:  #{options[:adapter]}"
    IO.puts "  encoding: #{options[:encoding]}"
    IO.puts "  pool:     #{Keyword.get(options, :pool)}"
    IO.puts "  username: #{Keyword.get(options, :username, "default_user")}"
    IO.puts "  password: #{Keyword.get_values(options, :password)}"
    IO.puts "  socket:   #{Keyword.fetch!(options, :socket)}"
  end
end
```

```iex
iex> Database.configures("development", adapter: "mysql2", encoding: "ascii", password: "new_password")
development:
  adapter:  mysql2
  encoding: ascii
  pool:     5
  username: default_user
  password: new_password
  socket:   /tmp/mysql.sock
```

`options[:adapter]` で取り出す以外にも Keyword モジュール、Enum モジュールの関数を使うこともできる。

# マップ

キー・値のセットのデータを扱いたい際に、幅広く使われる型

```iex
iex> map = %{first_name: "yamada", last_name: "taro", birthday: 19991010}
%{birthday: 19991010, first_name: "yamada", last_name: "taro"}
iex> Map.keys map
[:birthday, :first_name, :last_name]
iex> Map.values map
[19991010, "yamada", "taro"]
iex> map.first_name
"yamada"
iex> Map.put map, :address, "Tokyo"
%{address: "Tokyo", birthday: 19991010, first_name: "yamada", last_name: "taro"}
iex> Map.pop map, :address
{nil, %{birthday: 19991010, first_name: "yamada", last_name: "taro"}}
iex> Map.pop map, :first_name
{"yamada", %{birthday: 19991010, last_name: "taro"}}
```

# マップのパターンマッチ

マップは指定したキーと値があるかを判定する際によく使われる。
例えば以下の例はマッチが成功する。

```iex
iex> user = %{name: "taro", address: "Japan"}
%{address: "Japan", name: "taro"}

# name key が存在するか
iex> %{name: a} = user
%{address: "Japan", name: "taro"}
iex> a
"taro"

# name, address key が存在するか
iex> %{name: _, address: _} = user
%{address: "Japan", name: "taro"}

# name の値は taro か
iex> %{name: "taro"} = user
%{address: "Japan", name: "taro"}
```

次の様に、値が一致しない、key が存在しないなどの場合はマッチが失敗する。

```iex
iex> %{name: "hanako"} = user
** (MatchError) no match of right hand side value: %{address: "Japan", name: "taro"}

iex> %{age: _} = user
** (MatchError) no match of right hand side value: %{address: "Japan", name: "taro"}
```

パターンマッチを使って指定したキーに関連した値を取り出せることを利用して、`for` で以下のような実装ができる。

```ex
users = [
  %{name: "taro", address: "Japan"},
  %{name: "tom", address: "US"},
  %{name: "hanako", address: "Japan"},
  %{name: "mike", address: "US"}
]

for user = %{address: country} <- users, country == "Japan", do: user

#=> [%{address: "Japan", name: "taro"}, %{address: "Japan", name: "hanako"}]
```

`address` の値を取り出して、`"Japan"` の user を出力している。

ガード節を使って、複数の body を実装した例。

```ex
defmodule Sample do
  def evaluate(%{name: name, score: score})
  when score > 40 do
    IO.puts "#{name} is an excellent player"
  end

  def evaluate(%{name: name, score: score})
  when score < 10 do
    IO.puts "#{name} is a poor player"
  end

  def evaluate(player) do
    IO.puts "#{player.name} is an average player"
  end
end
```

```iex
iex> users = [
...>   %{name: "Taro", score: 50},
...>   %{name: "Tom", score: 40},
...>   %{name: "Hanako", score: 20},
...>   %{name: "Mike", score: 5}
...> ]

iex> users |> Enum.each(&Sample.evaluate/1)
Taro is an excellent player
Tom is an average player
Hanako is an average player
Mike is a poor player
```

## キーへの値の束縛

キーはパターンマッチによる束縛はできない

```iex
iex> %{key1: a} = %{key1: 1, key2: 2}
%{key1: 1, key2: 2}
iex> a
1

iex> %{a: 1} = %{key1: 1, key2: 2}
** (MatchError) no match of right hand side value: %{key1: 1, key2: 2}
```

## 変数キーをパターンマッチに使う

変数キーをパターンマッチに使用する場合は、以前見たように[ピン演算子](http://qiita.com/pekepek/items/cea917c8ed445da1bc24)を使用する。

```iex
iex> for key <- [:name, :score] do
...>   %{^key => value} = player
...>   value
...> end
["Taro", 50]
```

# マップの更新

パイプ文字を使うと簡単に値を置き換えた、新しい Map を作ることができる

```iex
iex> m = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
iex> %{m | a: "update"}
%{a: "update", b: 2, c: 3}
iex> %{m | b: "update"}
%{a: 1, b: "update", c: 3}
iex> %{m | c: "update"}
%{a: 1, b: 2, c: "update"}
```

新しい key => value を追加したい場合は、`Map.put_new/3` を使うと良い

```iex
iex> Map.put_new(m, :d, "update")
%{a: 1, b: 2, c: 3, d: "update"}
```

# 構造体

`%{}` とあれば Map である、ということはわかるが、どんなキーを持つのか？キーはどんなデフォルト値を持つのか？型はあるのか？などの情報はわからない。それらを明確に宣言することができるのが構造体である。

構造体は制限のついた Map になっていて、キーはアトムになっており、Map の一部の機能が使えない。
モジュールになっていて、`defstruct` でどんなキー・バリューをもっているかを宣言する。

```ex
defmodule Player do
  defstruct name: "", team: "default_team", score: 0
end
```

```iex
iex> %Player{}
%Player{name: "", score: 0, team: "default_team"}
iex> %Player{name: "tarokichi"}
%Player{name: "tarokichi", score: 0, team: "default_team"}
iex> %Player{name: "tarokichi", score: 100}
%Player{name: "tarokichi", score: 100, team: "default_team"}

iex> player_1 = %Player{name: "tarokichi", score: 100, team: "yellow"}
%Player{name: "tarokichi", score: 100, team: "yellow"}
iex> player_1.name
"tarokichi"
iex> player_1.team
"yellow"
```

パターンマッチで値を取得することもできる

```iex
iex> %Player{name: player_1_name} = player_1
%Player{name: "tarokichi", score: 100, team: "yellow"}
iex> player_1_name
"tarokichi"
```

Map のようにパイプで値を更新できる

```iex
iex> player_2 = %Player{player_1 | name: "jiro"}
%Player{name: "jiro", score: 100, team: "yellow"}
iex> player_2
%Player{name: "jiro", score: 100, team: "yellow"}
```

構造体を使った関数を定義した例

```ex
defmodule Player do
  defstruct name: "", team: "default_team", score: 0

  def print_info(player = %Player{name: name}) when name != "" do
    IO.puts "name: #{player.name}, team: #{player.team}, score: #{player.score}"
  end

  def print_info(%Player{}) do
    raise "name is empty!!"
  end
end
```

```iex
iex> Player.print_info(%Player{name: "taro"})
name: taro, team: default_team, score: 0
:ok
iex> Player.print_info(%Player{})
** (RuntimeError) name is empty!!
    iex:184: Player.print_info/1
iex> Player.print_info(%{})
** (FunctionClauseError) no function clause matching in Player.print_info/1
    iex:179: Player.print_info(%{})
```

Player 以外を渡すとエラーになっていることがわかる。

# 入れ子になった辞書構造体

以下の様に構造体を入れ子にすることもできる。

```ex
defmodule Player do
  defstruct name: "", team: "default_team", score: 0
end

defmodule Competition do
  defstruct winner: %Player{}, name: "", prize: ""
end
```

Competition の winner が Player 構造体になっている。

```iex
iex> taro = %Player{name: "Yamada Taro"}
%Player{name: "Yamada Taro", score: 0, team: "default_team"}
iex> c1 = %Competition{winner: taro, name: "Zenkoku-Taikai", prize: "$10,000"}
%Competition{name: "Zenkoku-Taikai", prize: "$10,000",
 winner: %Player{name: "Yamada Taro", score: 0, team: "default_team"}}
```

値には `.` をつなげる事でアクセスできる。

```iex
iex> c1.winner.name
"Yamada Taro"
iex> c1.winner.team
"default_team"
```

通常の構造体と同様にパイプ文字で値を更新することができる。
例えば、winner の team を "yellow team" に更新したい場合は以下のようになる。

```iex
iex> c1
%Competition{name: "Zenkoku-Taikai", prize: "$10,000",
 winner: %Player{name: "Yamada Taro", score: 0, team: "default_team"}}
iex> taro
%Player{name: "Yamada Taro", score: 0, team: "default_team"}

iex> %Competition{c1 | winner: %Player{taro | team: "yellow team"}}
%Competition{name: "Zenkoku-Taikai", prize: "$10,000",
 winner: %Player{name: "Yamada Taro", score: 0, team: "yellow team"}}
```

このように Player の team を更新したいだけなのに、Competition 構造体を介して更新させなければならず、とても冗長になってしまう。
そこで、こういう場合は `put_in` を使って更新する。

```iex
iex> put_in(c1.winner.team, "red team")
%Competition{name: "Zenkoku-Taikai", prize: "$10,000",
 winner: %Player{name: "Yamada Taro", score: 0, team: "red team"}}
```

似ている関数で `update_in` というのもある。
これは、与えた関数を適用する。

```iex
update_in(c1.winner.score, &(&1 + 100))
%Competition{name: "Zenkoku-Taikai", prize: "$10,000",
 winner: %Player{name: "Yamada Taro", score: 100, team: "default_team"}}
```

上記の put_in などの例はマクロで値を渡しているが、put_in には関数が存在し、リストと key を渡して値を更新することもできる。

```iex
iex> player = %{name: %{first: "Taro", last: "Yamada"}}
%{name: %{first: "Taro", last: "Yamada"}}
iex> put_in(player, [:name, :first], "Hanako")
%{name: %{first: "Hanako", last: "Yamada"}}
iex> put_in(player, [:name, :last], "Tanaka")
%{name: %{first: "Taro", last: "Tanaka"}}
```

# セット

セットの実装として MapSet が用意されている

```iex
iex> set = MapSet.new([1, 2, 3])
#MapSet<[1, 2, 3]>
iex> MapSet.delete(set, 2)
#MapSet<[1, 3]>
iex> MapSet.put(set, 4)
#MapSet<[1, 2, 3, 4]>
iex> set2 = MapSet.new([1, 2, 3, 5, 6])
#MapSet<[1, 2, 3, 5, 6]>
iex> MapSet.difference(set2, set)
#MapSet<[5, 6]>
iex> MapSet.union(set2, set)
```