Add has_key/2 dictionary method.
[hope.git] / src / hope_kv_list.erl
1 %%%----------------------------------------------------------------------------
2 %%% Equivalent to stdlib's orddict, but with a pretty (IMO), uniform interface.
3 %%%----------------------------------------------------------------------------
4 -module(hope_kv_list).
5
6 -include_lib("hope_kv_list.hrl").
7
8 -behavior(hope_gen_dictionary).
9
10 -export_type(
11 [ t/2
12 ]).
13
14 -export(
15 [ empty/0
16 , get/2 % get option
17 , get/3 % get existing or default
18 , get/4 % get existing if valid, or default
19 , set/3
20 , update/3
21 , pop/2
22 , iter/2
23 , map/2
24 , filter/2
25 , fold/3
26 , of_kv_list/1
27 , to_kv_list/1
28 , has_key/2
29 , find_unique_presence_violations/2 % No optional keys
30 , find_unique_presence_violations/3 % Specify optional keys
31 , validate_unique_presence/2 % No optional keys
32 , validate_unique_presence/3 % Specify optional keys
33 , presence_violations_to_list/1
34 ]).
35
36
37 -type t(K, V) ::
38 [{K, V}].
39
40 -type presence_violations(A) ::
41 % This is a hack to effectively parametarize the types of record fields.
42 % IMPORTANT: Make sure that the order of fields matches the definition of
43 % #hope_kv_list_presence_violations
44 { hope_kv_list_presence_violations
45 , [A] % keys_missing
46 , [A] % keys_duplicated
47 , [A] % keys_unsupported
48 }.
49
50 -type presence_error(A) ::
51 {keys_missing , [A]}
52 | {keys_duplicated , [A]}
53 | {keys_unsupported , [A]}
54 .
55
56
57 %% ============================================================================
58 %% API
59 %% ============================================================================
60
61 -spec empty() ->
62 [].
63 empty() ->
64 [].
65
66 -spec get(t(K, V), K) ->
67 hope_option:t(V).
68 get(T, K) ->
69 case lists:keyfind(K, 1, T)
70 of false -> none
71 ; {K, V} -> {some, V}
72 end.
73
74 -spec get(t(K, V), K, V) ->
75 V.
76 get(T, K, Default) ->
77 Vopt = get(T, K),
78 hope_option:get(Vopt, Default).
79
80 -spec get(t(K, V), K, V, fun((V) -> boolean())) ->
81 V.
82 get(T, K, Default, IsValid) ->
83 VOpt1 = get(T, K),
84 VOpt2 = hope_option:validate(VOpt1, IsValid),
85 hope_option:get(VOpt2, Default).
86
87 -spec set(t(K, V), K, V) ->
88 t(K, V).
89 set(T, K, V) ->
90 lists:keystore(K, 1, T, {K, V}).
91
92 -spec update(t(K, V), K, fun((hope_option:t(V)) -> V)) ->
93 t(K, V).
94 update(T, K, F) ->
95 V1Opt = get(T, K),
96 V2 = F(V1Opt),
97 % TODO: Eliminate the 2nd lookup.
98 set(T, K, V2).
99
100 -spec pop(t(K, V), K) ->
101 {hope_option:t(V), t(K, V)}.
102 pop(T1, K) ->
103 case lists:keytake(K, 1, T1)
104 of {value, {K, V}, T2} -> {{some, V}, T2}
105 ; false -> {none , T1}
106 end.
107
108 -spec iter(t(K, V), fun((K, V) -> ok)) ->
109 ok.
110 iter(T, F1) ->
111 F2 = lift_map(F1),
112 lists:foreach(F2, T).
113
114 -spec map(t(K, V), fun((K, V) -> V)) ->
115 t(K, V).
116 map(T, F1) ->
117 F2 = fun ({K, _}=X) -> {K, apply_map(F1, X)} end,
118 lists:map(F2, T).
119
120 -spec filter(t(K, V), fun((K, V) -> boolean())) ->
121 t(K, V).
122 filter(T, F1) ->
123 F2 = lift_map(F1),
124 lists:filter(F2, T).
125
126 -spec fold(t(K, V), fun((K, V, Acc) -> Acc), Acc) ->
127 Acc.
128 fold(T, F1, Accumulator) ->
129 F2 = fun ({K, V}, Acc) -> F1(K, V, Acc) end,
130 lists:foldl(F2, Accumulator, T).
131
132 -spec to_kv_list(t(K, V)) ->
133 [{K, V}].
134 to_kv_list(T) ->
135 T.
136
137 -spec of_kv_list([{K, V}]) ->
138 t(K, V).
139 of_kv_list(List) ->
140 % TODO: Decide if validation is to be done here. Do so if yes.
141 List.
142
143 -spec validate_unique_presence(T, [K]) ->
144 hope_result:t(T, [presence_error(K)])
145 when T :: t(K, _V).
146 validate_unique_presence(T, KeysRequired) ->
147 KeysOptional = [],
148 validate_unique_presence(T, KeysRequired, KeysOptional).
149
150 -spec validate_unique_presence(t(K, _V), [K], [K]) ->
151 hope_result:t(T, [presence_error(K)])
152 when T :: t(K, _V).
153 validate_unique_presence(T, KeysRequired, KeysOptional) ->
154 case find_unique_presence_violations(T, KeysRequired, KeysOptional)
155 of #hope_kv_list_presence_violations
156 { keys_missing = []
157 , keys_duplicated = []
158 , keys_unsupported = []
159 } ->
160 {ok, T}
161 ; #hope_kv_list_presence_violations{}=Violations ->
162 {error, presence_violations_to_list(Violations)}
163 end.
164
165 -spec find_unique_presence_violations(t(K, _V), [K]) ->
166 presence_violations(K).
167 find_unique_presence_violations(T, KeysRequired) ->
168 KeysOptional = [],
169 find_unique_presence_violations(T, KeysRequired, KeysOptional).
170
171 -spec find_unique_presence_violations(t(K, _V), [K], [K]) ->
172 presence_violations(K).
173 find_unique_presence_violations(T, KeysRequired, KeysOptional) ->
174 KeysSupported = KeysRequired ++ KeysOptional,
175 KeysGiven = [K || {K, _V} <- T],
176 KeysGivenUnique = lists:usort(KeysGiven),
177 KeysDuplicated = lists:usort(KeysGiven -- KeysGivenUnique),
178 KeysMissing = KeysRequired -- KeysGivenUnique,
179 KeysUnsupported = KeysGivenUnique -- KeysSupported,
180 #hope_kv_list_presence_violations
181 { keys_missing = KeysMissing
182 , keys_duplicated = KeysDuplicated
183 , keys_unsupported = KeysUnsupported
184 }.
185
186 -spec presence_violations_to_list(presence_violations(K)) ->
187 [presence_error(K)].
188 presence_violations_to_list(#hope_kv_list_presence_violations
189 { keys_missing = KeysMissing
190 , keys_duplicated = KeysDuplicated
191 , keys_unsupported = KeysUnsupported
192 }) ->
193 ErrorMissing =
194 case KeysMissing
195 of [] -> []
196 ; [_|_] -> [{keys_missing, KeysMissing}]
197 end,
198 ErrorDups =
199 case KeysDuplicated
200 of [] -> []
201 ; [_|_] -> [{keys_duplicated, KeysDuplicated}]
202 end,
203 ErrorUnsupported =
204 case KeysUnsupported
205 of [] -> []
206 ; [_|_] -> [{keys_unsupported, KeysUnsupported}]
207 end,
208 ErrorDups ++ ErrorMissing ++ ErrorUnsupported.
209
210 -spec has_key(t(K, _), K) ->
211 boolean().
212 has_key(T, K1) ->
213 lists:any(fun ({K2, _}) -> K1 =:= K2 end, T).
214
215 %% ============================================================================
216 %% Helpers
217 %% ============================================================================
218
219 -spec lift_map(F) ->
220 G
221 when F :: fun(( K, V1 ) -> V2)
222 , G :: fun(({K, V1}) -> V2)
223 .
224 lift_map(F) ->
225 fun (X) -> apply_map(F, X) end.
226
227 -spec apply_map(fun((K, V1) -> V2), {K, V1}) ->
228 V2.
229 apply_map(F, {K, V}) ->
230 F(K, V).
This page took 0.083292 seconds and 4 git commands to generate.