概要
パターンマッチを使ったマップの利用方法、構造体や辞書型の使い方を解説している
マップとキーワードリストどちらを使うべきか
以下の条件で、キーワードリスト [a: 1, b: 2, c: 3]
とマップ %{a: 1, b: 2, c: 3}
をどうやって使い分ける
キーワードリスト
- 同じキーが 2 回以上現れる
- 要素の順番を保証する必要がある
マップ
- 内容について、パターンマッチを行う必要がある
- その他諸々
キーワードリスト
主に関数に渡されるオプションの用途で使われる。
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> 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> 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> 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> %{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
で以下のような実装ができる。
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 を実装した例。
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> 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> %{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}
変数キーをパターンマッチに使う
変数キーをパターンマッチに使用する場合は、以前見たようにピン演算子を使用する。
iex> for key <- [:name, :score] do
...> %{^key => value} = player
...> value
...> end
["Taro", 50]
マップの更新
パイプ文字を使うと簡単に値を置き換えた、新しい Map を作ることができる
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> Map.put_new(m, :d, "update")
%{a: 1, b: 2, c: 3, d: "update"}
構造体
%{}
とあれば Map である、ということはわかるが、どんなキーを持つのか?キーはどんなデフォルト値を持つのか?型はあるのか?などの情報はわからない。それらを明確に宣言することができるのが構造体である。
構造体は制限のついた Map になっていて、キーはアトムになっており、Map の一部の機能が使えない。
モジュールになっていて、defstruct
でどんなキー・バリューをもっているかを宣言する。
defmodule Player do
defstruct name: "", team: "default_team", score: 0
end
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> %Player{name: player_1_name} = player_1
%Player{name: "tarokichi", score: 100, team: "yellow"}
iex> player_1_name
"tarokichi"
Map のようにパイプで値を更新できる
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"}
構造体を使った関数を定義した例
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> 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 以外を渡すとエラーになっていることがわかる。
入れ子になった辞書構造体
以下の様に構造体を入れ子にすることもできる。
defmodule Player do
defstruct name: "", team: "default_team", score: 0
end
defmodule Competition do
defstruct winner: %Player{}, name: "", prize: ""
end
Competition の winner が Player 構造体になっている。
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> c1.winner.name
"Yamada Taro"
iex> c1.winner.team
"default_team"
通常の構造体と同様にパイプ文字で値を更新することができる。
例えば、winner の team を "yellow team" に更新したい場合は以下のようになる。
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> 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
というのもある。
これは、与えた関数を適用する。
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> 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> 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)