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