Erlang の maps モジュールを一通り試す - 準備
OTP 17.0 から maps モジュールが追加された。一通り試す。
今回は、その準備として、公式ドキュメントバージョン10.5 : http://erlang.org/documentation/doc-10.5/doc/reference_manual/expressions.html#map-expressions のマップの式について学ぶ。
マップの式
マップは、キーと値の関連付けの集合のこと。これらの関連付けは#{
と}
でカプセル化される。"key"
と 42 の関連付け作成するには、下記のように書く。
> #{"key" => 42}. #{"key" => 42}
マップに関連付けは複数含めることができる。
#{ K1 => V1, .., Kn => Vn }
空のマップを作成したいときは下記のように書くことができる。
#{}
マップ内のすべてのキーと値は term() を使うことができる。式が最初に評価され、その結果の term() がキー・値として使用される。
キーと値は =>
で区切られ、関連付けはカンマで区切られる。
例: ここで A, B は任意の式、f() は関数。
M0 = #{}, M1 = #{a => <<"hello">>}, M2 = #{1 => 2, b => b}, M3 = #{k => {A,B}}, M4 = #{{"w", 1} => f()}.
同じキーが宣言されている場合、後者のキーが優先される。
> #{1 => a, 1 => b}. #{1 => b} > #{1.0 => a, 1 => b}. #{1 => b,1.0 => a}
キーまたは値を構成する式が評価される順序は定義されていない。コードに記述した順番で実行されるとは限らない。キーと値のペアの順序は、同じキーが存在する場合を除いて、関連性なはない。
マップの更新
マップの更新の構文は、マップの構築と同様。
M#{ K => V }
M
はマップ型のtermで、K
およびV
は任意の式。
キー K
がマップ内の既存のキーと一致しない場合、キー K
から値 V
への新しい関連付けが作成される。
キー K
がマップ M
の既存のキーと一致する場合、関連する値は新しい値 V
に置き換えられる。どちらの場合も、評価されたマップの式は新しいマップを返す。
M
がマップ型ではない場合、badmapの例外がスローされる。
既知の値のみを更新するには、下記の構文を使用する。
M#{ K := V }
キー K
がマップ M
の既存のキーと一致しない場合、実行時に badarg の例外がトリガーされる。
一致するキー K
がマップ M
に存在する場合、その関連する値は新しい値 V
に置き換えられ、評価されたマップの式は新しいマップを返す。
例:
> M0 = #{}. #{} > M1 = M0#{a => 0}. #{a => 0} > M2 = M1#{a => 1, b => 2}. #{a => 1,b => 2} > M3 = M2#{"function" => fun(X) -> is_atom(X) end}. #{a => 1,b => 2,"function" => #Fun<erl_eval.6.99386804>} > M4 = M3#{a := 2, b := 3}. #{a => 2,b => 3,"function" => #Fun<erl_eval.6.99386804>}
余談だが、既存のキーにないものを:=
で更新しようとすると badarg が発生するとのことだったが、OTP 22で実際に例外を発生させてみると、badarg ではなく、badkey でクラッシュした。
> M = #{1 => a}. #{1 => a} > M#{1.0 => b}. #{1 => a,1.0 => b} > M#{1 := b}. #{1 => b} > M#{1.0 := b}. ** exception error: {badkey,1.0} in function maps:update/3 called as maps:update(1.0,b,#{1 => a}) in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 256) in call from lists:foldl/3 (lists.erl, line 1263)
更新においても、マップの作成時と同様に、キーと値の式が評価される順序は定義されていない。また、更新におけるキーと値のペアの順序は、2つのキーが一致する場合を除き、関係がない。2つのキーが一致する場合は、後者が優先される。
> M = #{1 => a}. #{1 => a} > M#{1 := b, 1 :=c }. #{1 => c} > M#{1 := c, 1 :=b }. #{1 => b}
マップのパターンマッチ
マップのキーと値の関連付けのマッチングは下記のように行われる。
#{ K := V } = M
M
は任意のマップ。キー K
はバインドされた変数またはリテラルを持つ式でなければならない。V
はバインドされた変数またはバインドされていない変数を持つ任意のパターン。
変数 V
がバインドされていない場合、キー K
に関連付けられた値がバインドされる。キー K
はマップMに存在する必要がある。
変数 V
がバインドされている場合、M
の K
に関連付けられている値と一致する必要がある。
例:下記は変数 B
に 2
がバインドされる。
> M = #{"tuple" => {1,2}}. #{"tuple" => {1,2}} > #{"tuple" := {1,B}} = M. #{"tuple" => {1,2}} > B. 2
:=
ではなく=>
を使おうとすると、illegal pattern
になる。マップの一致では、関連付けの区切り文字として:=
のみが許されている。マップの一致でキーが宣言される順序には関連性はない。
> #{"tuple" => {1,B}} = M. * 1: illegal pattern
同様に、マップの複数の値を一致させることができる。
#{ K1 := V1, .., Kn := Vn } = M
重複キーのマッチングは許される。キーに関連付けられた各パターンと一致する。
> M = #{a => 1}. #{a => 1} > #{a := A1, a := A2} = M. #{a => 1} > A1. 1 > A2. 1
空のマップリテラルに対して式を一致させると、その型は一致しますが、変数はバインドされない。
#{} = Expr
上記の式は、Expr
の型がマップであれば一致し、そうでなければ badmatch
になる。
マッチング構文
関数の頭でのキーのマッチングが許されている。
%% only start if not_started handle_call(start, From, #{ state := not_started } = S) -> ... {reply, ok, S#{ state := start }}; %% only change if started handle_call(change, From, #{ state := start } = S) -> ... {reply, ok, S#{ state := changed }};
ガードでのマップ
マップは、ガードでも使える。
erlang モジュールの is_map/1
関数、map_size/1
関数がガードで扱える。