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