@@ -31,6 +31,70 @@ defmodule Dict do
31
31
32
32
Dictionaries are required to implement all operations
33
33
using the match (`===`) operator.
34
+
35
+ ## Default implementation
36
+
37
+ Default implementations for some functions in the `Dict` module
38
+ are provided via `use Dict`.
39
+
40
+ For example:
41
+
42
+ defmodule MyDict do
43
+ use Dict.Behaviour
44
+
45
+ # implement required functions (see below)
46
+ # override default implementations if optimization
47
+ # is needed
48
+ end
49
+
50
+ The client module must contain the following functions:
51
+
52
+ * `delete/2`
53
+ * `fetch/2`
54
+ * `put/3`
55
+ * `reduce/3`
56
+ * `size/1`
57
+
58
+ All functions, except `reduce/3`, are required by the Dict behaviour.
59
+ `reduce/3` must be implemtented as per the Enumerable protocol.
60
+
61
+ Based on these functions, `Dict` generates default implementations
62
+ for the following functions:
63
+
64
+ * `drop/2`
65
+ * `equal?/2`
66
+ * `fetch!/2`
67
+ * `get/2`
68
+ * `get/3`
69
+ * `has_key?/2`
70
+ * `keys/1`
71
+ * `merge/2`
72
+ * `merge/3`
73
+ * `pop/2`
74
+ * `pop/3`
75
+ * `put_new/3`
76
+ * `split/2`
77
+ * `take/2`
78
+ * `to_list/1`
79
+ * `update/4`
80
+ * `update!/3`
81
+ * `values/1`
82
+
83
+ All of these functions are defined as overridable, so you can provide
84
+ your own implementation if needed.
85
+
86
+ Note you can also test your custom module via `Dict`'s doctests:
87
+
88
+ defmodule MyDict do
89
+ # ...
90
+ end
91
+
92
+ defmodule MyTests do
93
+ use ExUnit.Case
94
+ doctest Dict
95
+ defp dict_impl, do: MyDict
96
+ end
97
+
34
98
"""
35
99
36
100
use Behaviour
@@ -63,6 +127,146 @@ defmodule Dict do
63
127
defcallback update! ( t , key , ( value -> value ) ) :: t | no_return
64
128
defcallback values ( t ) :: list ( value )
65
129
130
+ defmacro __using__ ( _ ) do
131
+ # Use this import to guarantee proper code expansion
132
+ import Kernel , except: [ size: 1 ]
133
+
134
+ quote do
135
+ @ behaviour Dict
136
+
137
+ def get ( dict , key , default \\ nil ) do
138
+ case fetch ( dict , key ) do
139
+ { :ok , value } -> value
140
+ :error -> default
141
+ end
142
+ end
143
+
144
+ def fetch! ( dict , key ) do
145
+ case fetch ( dict , key ) do
146
+ { :ok , value } -> value
147
+ :error -> raise KeyError , key: key , term: dict
148
+ end
149
+ end
150
+
151
+ def has_key? ( dict , key ) do
152
+ match? { :ok , _ } , fetch ( dict , key )
153
+ end
154
+
155
+ def put_new ( dict , key , value ) do
156
+ case has_key? ( dict , key ) do
157
+ true -> dict
158
+ false -> put ( dict , key , value )
159
+ end
160
+ end
161
+
162
+ def drop ( dict , keys ) do
163
+ Enum . reduce ( keys , dict , & delete ( & 2 , & 1 ) )
164
+ end
165
+
166
+ def take ( dict , keys ) do
167
+ Enum . reduce ( keys , new , fn key , acc ->
168
+ case fetch ( dict , key ) do
169
+ { :ok , value } -> put ( acc , key , value )
170
+ :error -> acc
171
+ end
172
+ end )
173
+ end
174
+
175
+ def to_list ( dict ) do
176
+ reduce ( dict , { :cont , [ ] } , fn
177
+ kv , acc -> { :cont , [ kv | acc ] }
178
+ end ) |> elem ( 1 ) |> :lists . reverse
179
+ end
180
+
181
+ def keys ( dict ) do
182
+ reduce ( dict , { :cont , [ ] } , fn
183
+ { k , _ } , acc -> { :cont , [ k | acc ] }
184
+ end ) |> elem ( 1 ) |> :lists . reverse
185
+ end
186
+
187
+ def values ( dict ) do
188
+ reduce ( dict , { :cont , [ ] } , fn
189
+ { _ , v } , acc -> { :cont , [ v | acc ] }
190
+ end ) |> elem ( 1 ) |> :lists . reverse
191
+ end
192
+
193
+ def equal? ( dict1 , dict2 ) do
194
+ # Use this import to avoid conflicts in the user code
195
+ import Kernel , except: [ size: 1 ]
196
+
197
+ case size ( dict1 ) == size ( dict2 ) do
198
+ false -> false
199
+ true ->
200
+ reduce ( dict1 , { :cont , true } , fn ( { k , v } , _acc ) ->
201
+ case fetch ( dict2 , k ) do
202
+ { :ok , ^ v } -> { :cont , true }
203
+ _ -> { :halt , false }
204
+ end
205
+ end ) |> elem ( 1 )
206
+ end
207
+ end
208
+
209
+ def merge ( dict1 , dict2 , fun \\ fn ( _k , _v1 , v2 ) -> v2 end ) do
210
+ # Use this import to avoid conflicts in the user code
211
+ import Kernel , except: [ size: 1 ]
212
+
213
+ if size ( dict1 ) < size ( dict2 ) do
214
+ reduce ( dict1 , { :cont , dict2 } , fn { k , v1 } , acc ->
215
+ { :cont , update ( acc , k , v1 , & fun . ( k , v1 , & 1 ) ) }
216
+ end )
217
+ else
218
+ reduce ( dict2 , { :cont , dict1 } , fn { k , v2 } , acc ->
219
+ { :cont , update ( acc , k , v2 , & fun . ( k , & 1 , v2 ) ) }
220
+ end )
221
+ end |> elem ( 1 )
222
+ end
223
+
224
+ def update ( dict , key , initial , fun ) do
225
+ case fetch ( dict , key ) do
226
+ { :ok , value } ->
227
+ put ( dict , key , fun . ( value ) )
228
+ :error ->
229
+ put ( dict , key , initial )
230
+ end
231
+ end
232
+
233
+ def update! ( dict , key , fun ) do
234
+ case fetch ( dict , key ) do
235
+ { :ok , value } ->
236
+ put ( dict , key , fun . ( value ) )
237
+ :error ->
238
+ raise KeyError , key: key , term: dict
239
+ end
240
+ end
241
+
242
+ def pop ( dict , key , default \\ nil ) do
243
+ case fetch ( dict , key ) do
244
+ { :ok , value } ->
245
+ { value , delete ( dict , key ) }
246
+ :error ->
247
+ { default , dict }
248
+ end
249
+ end
250
+
251
+ def split ( dict , keys ) do
252
+ Enum . reduce ( keys , { new , dict } , fn key , { inc , exc } = acc ->
253
+ case fetch ( exc , key ) do
254
+ { :ok , value } ->
255
+ { put ( inc , key , value ) , delete ( exc , key ) }
256
+ :error ->
257
+ acc
258
+ end
259
+ end )
260
+ end
261
+
262
+ defoverridable merge: 2 , merge: 3 , equal?: 2 , to_list: 1 , keys: 1 ,
263
+ values: 1 , take: 2 , drop: 2 , get: 2 , get: 3 , fetch!: 2 ,
264
+ has_key?: 2 , put_new: 3 , pop: 2 , pop: 3 , split: 2 ,
265
+ update: 4 , update!: 3
266
+ end
267
+ end
268
+
269
+
66
270
defmacrop target ( dict ) do
67
271
quote do
68
272
case unquote ( dict ) do
0 commit comments