1defmodule Dict do
2  @moduledoc ~S"""
3  Generic API for dictionaries.
4
5  If you need a general dictionary, use the `Map` module.
6  If you need to manipulate keyword lists, use `Keyword`.
7
8  To convert maps into keywords and vice-versa, use the
9  `new` function in the respective modules.
10  """
11
12  @moduledoc deprecated: "Use Map or Keyword modules instead"
13
14  @type key :: any
15  @type value :: any
16  @type t :: list | map
17
18  message =
19    "Use the Map module for working with maps or the Keyword module for working with keyword lists"
20
21  defmacro __using__(_) do
22    # Use this import to guarantee proper code expansion
23    import Kernel, except: [size: 1]
24
25    if __CALLER__.module != HashDict do
26      IO.warn("use Dict is deprecated. " <> unquote(message), Macro.Env.stacktrace(__CALLER__))
27    end
28
29    quote do
30      message = "Use maps and the Map module instead"
31
32      @deprecated message
33      def get(dict, key, default \\ nil) do
34        case fetch(dict, key) do
35          {:ok, value} -> value
36          :error -> default
37        end
38      end
39
40      @deprecated message
41      def get_lazy(dict, key, fun) when is_function(fun, 0) do
42        case fetch(dict, key) do
43          {:ok, value} -> value
44          :error -> fun.()
45        end
46      end
47
48      @deprecated message
49      def get_and_update(dict, key, fun) do
50        current_value = get(dict, key)
51        {get, new_value} = fun.(current_value)
52        {get, put(dict, key, new_value)}
53      end
54
55      @deprecated message
56      def fetch!(dict, key) do
57        case fetch(dict, key) do
58          {:ok, value} -> value
59          :error -> raise KeyError, key: key, term: dict
60        end
61      end
62
63      @deprecated message
64      def has_key?(dict, key) do
65        match?({:ok, _}, fetch(dict, key))
66      end
67
68      @deprecated message
69      def put_new(dict, key, value) do
70        case has_key?(dict, key) do
71          true -> dict
72          false -> put(dict, key, value)
73        end
74      end
75
76      @deprecated message
77      def put_new_lazy(dict, key, fun) when is_function(fun, 0) do
78        case has_key?(dict, key) do
79          true -> dict
80          false -> put(dict, key, fun.())
81        end
82      end
83
84      @deprecated message
85      def drop(dict, keys) do
86        Enum.reduce(keys, dict, &delete(&2, &1))
87      end
88
89      @deprecated message
90      def take(dict, keys) do
91        Enum.reduce(keys, new(), fn key, acc ->
92          case fetch(dict, key) do
93            {:ok, value} -> put(acc, key, value)
94            :error -> acc
95          end
96        end)
97      end
98
99      @deprecated message
100      def to_list(dict) do
101        reduce(dict, {:cont, []}, fn kv, acc -> {:cont, [kv | acc]} end)
102        |> elem(1)
103        |> :lists.reverse()
104      end
105
106      @deprecated message
107      def keys(dict) do
108        reduce(dict, {:cont, []}, fn {k, _}, acc -> {:cont, [k | acc]} end)
109        |> elem(1)
110        |> :lists.reverse()
111      end
112
113      @deprecated message
114      def values(dict) do
115        reduce(dict, {:cont, []}, fn {_, v}, acc -> {:cont, [v | acc]} end)
116        |> elem(1)
117        |> :lists.reverse()
118      end
119
120      @deprecated message
121      def equal?(dict1, dict2) do
122        # Use this import to avoid conflicts in the user code
123        import Kernel, except: [size: 1]
124
125        case size(dict1) == size(dict2) do
126          false ->
127            false
128
129          true ->
130            reduce(dict1, {:cont, true}, fn {k, v}, _acc ->
131              case fetch(dict2, k) do
132                {:ok, ^v} -> {:cont, true}
133                _ -> {:halt, false}
134              end
135            end)
136            |> elem(1)
137        end
138      end
139
140      @deprecated message
141      def merge(dict1, dict2, fun \\ fn _k, _v1, v2 -> v2 end) do
142        # Use this import to avoid conflicts in the user code
143        import Kernel, except: [size: 1]
144
145        if size(dict1) < size(dict2) do
146          reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
147            {:cont, update(acc, k, v1, &fun.(k, v1, &1))}
148          end)
149        else
150          reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
151            {:cont, update(acc, k, v2, &fun.(k, &1, v2))}
152          end)
153        end
154        |> elem(1)
155      end
156
157      @deprecated message
158      def update(dict, key, default, fun) do
159        case fetch(dict, key) do
160          {:ok, value} ->
161            put(dict, key, fun.(value))
162
163          :error ->
164            put(dict, key, default)
165        end
166      end
167
168      @deprecated message
169      def update!(dict, key, fun) do
170        case fetch(dict, key) do
171          {:ok, value} ->
172            put(dict, key, fun.(value))
173
174          :error ->
175            raise KeyError, key: key, term: dict
176        end
177      end
178
179      @deprecated message
180      def pop(dict, key, default \\ nil) do
181        case fetch(dict, key) do
182          {:ok, value} ->
183            {value, delete(dict, key)}
184
185          :error ->
186            {default, dict}
187        end
188      end
189
190      @deprecated message
191      def pop_lazy(dict, key, fun) when is_function(fun, 0) do
192        case fetch(dict, key) do
193          {:ok, value} ->
194            {value, delete(dict, key)}
195
196          :error ->
197            {fun.(), dict}
198        end
199      end
200
201      @deprecated message
202      def split(dict, keys) do
203        Enum.reduce(keys, {new(), dict}, fn key, {inc, exc} = acc ->
204          case fetch(exc, key) do
205            {:ok, value} ->
206              {put(inc, key, value), delete(exc, key)}
207
208            :error ->
209              acc
210          end
211        end)
212      end
213
214      defoverridable merge: 2,
215                     merge: 3,
216                     equal?: 2,
217                     to_list: 1,
218                     keys: 1,
219                     values: 1,
220                     take: 2,
221                     drop: 2,
222                     get: 2,
223                     get: 3,
224                     fetch!: 2,
225                     has_key?: 2,
226                     put_new: 3,
227                     pop: 2,
228                     pop: 3,
229                     split: 2,
230                     update: 4,
231                     update!: 3,
232                     get_and_update: 3,
233                     get_lazy: 3,
234                     pop_lazy: 3,
235                     put_new_lazy: 3
236    end
237  end
238
239  defmacrop target(dict) do
240    quote do
241      case unquote(dict) do
242        %module{} -> module
243        %{} -> Map
244        dict when is_list(dict) -> Keyword
245        dict -> unsupported_dict(dict)
246      end
247    end
248  end
249
250  @deprecated message
251  @spec keys(t) :: [key]
252  def keys(dict) do
253    target(dict).keys(dict)
254  end
255
256  @deprecated message
257  @spec values(t) :: [value]
258  def values(dict) do
259    target(dict).values(dict)
260  end
261
262  @deprecated message
263  @spec size(t) :: non_neg_integer
264  def size(dict) do
265    target(dict).size(dict)
266  end
267
268  @deprecated message
269  @spec has_key?(t, key) :: boolean
270  def has_key?(dict, key) do
271    target(dict).has_key?(dict, key)
272  end
273
274  @deprecated message
275  @spec get(t, key, value) :: value
276  def get(dict, key, default \\ nil) do
277    target(dict).get(dict, key, default)
278  end
279
280  @deprecated message
281  @spec get_lazy(t, key, (() -> value)) :: value
282  def get_lazy(dict, key, fun) do
283    target(dict).get_lazy(dict, key, fun)
284  end
285
286  @deprecated message
287  @spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
288  def get_and_update(dict, key, fun) do
289    target(dict).get_and_update(dict, key, fun)
290  end
291
292  @deprecated message
293  @spec fetch(t, key) :: value
294  def fetch(dict, key) do
295    target(dict).fetch(dict, key)
296  end
297
298  @deprecated message
299  @spec fetch!(t, key) :: value
300  def fetch!(dict, key) do
301    target(dict).fetch!(dict, key)
302  end
303
304  @deprecated message
305  @spec put(t, key, value) :: t
306  def put(dict, key, val) do
307    target(dict).put(dict, key, val)
308  end
309
310  @deprecated message
311  @spec put_new(t, key, value) :: t
312  def put_new(dict, key, val) do
313    target(dict).put_new(dict, key, val)
314  end
315
316  @deprecated message
317  @spec put_new_lazy(t, key, (() -> value)) :: t
318  def put_new_lazy(dict, key, fun) do
319    target(dict).put_new_lazy(dict, key, fun)
320  end
321
322  @deprecated message
323  @spec delete(t, key) :: t
324  def delete(dict, key) do
325    target(dict).delete(dict, key)
326  end
327
328  @deprecated message
329  @spec merge(t, t) :: t
330  def merge(dict1, dict2) do
331    target1 = target(dict1)
332    target2 = target(dict2)
333
334    if target1 == target2 do
335      target1.merge(dict1, dict2)
336    else
337      do_merge(target1, dict1, dict2, fn _k, _v1, v2 -> v2 end)
338    end
339  end
340
341  @deprecated message
342  @spec merge(t, t, (key, value, value -> value)) :: t
343  def merge(dict1, dict2, fun) do
344    target1 = target(dict1)
345    target2 = target(dict2)
346
347    if target1 == target2 do
348      target1.merge(dict1, dict2, fun)
349    else
350      do_merge(target1, dict1, dict2, fun)
351    end
352  end
353
354  defp do_merge(target1, dict1, dict2, fun) do
355    Enumerable.reduce(dict2, {:cont, dict1}, fn {k, v}, acc ->
356      {:cont, target1.update(acc, k, v, fn other -> fun.(k, other, v) end)}
357    end)
358    |> elem(1)
359  end
360
361  @deprecated message
362  @spec pop(t, key, value) :: {value, t}
363  def pop(dict, key, default \\ nil) do
364    target(dict).pop(dict, key, default)
365  end
366
367  @deprecated message
368  @spec pop_lazy(t, key, (() -> value)) :: {value, t}
369  def pop_lazy(dict, key, fun) do
370    target(dict).pop_lazy(dict, key, fun)
371  end
372
373  @deprecated message
374  @spec update!(t, key, (value -> value)) :: t
375  def update!(dict, key, fun) do
376    target(dict).update!(dict, key, fun)
377  end
378
379  @deprecated message
380  @spec update(t, key, value, (value -> value)) :: t
381  def update(dict, key, default, fun) do
382    target(dict).update(dict, key, default, fun)
383  end
384
385  @deprecated message
386  @spec split(t, [key]) :: {t, t}
387  def split(dict, keys) do
388    target(dict).split(dict, keys)
389  end
390
391  @deprecated message
392  @spec drop(t, [key]) :: t
393  def drop(dict, keys) do
394    target(dict).drop(dict, keys)
395  end
396
397  @deprecated message
398  @spec take(t, [key]) :: t
399  def take(dict, keys) do
400    target(dict).take(dict, keys)
401  end
402
403  @deprecated message
404  @spec empty(t) :: t
405  def empty(dict) do
406    target(dict).empty(dict)
407  end
408
409  @deprecated message
410  @spec equal?(t, t) :: boolean
411  def equal?(dict1, dict2) do
412    target1 = target(dict1)
413    target2 = target(dict2)
414
415    cond do
416      target1 == target2 ->
417        target1.equal?(dict1, dict2)
418
419      target1.size(dict1) == target2.size(dict2) ->
420        Enumerable.reduce(dict2, {:cont, true}, fn {k, v}, _acc ->
421          case target1.fetch(dict1, k) do
422            {:ok, ^v} -> {:cont, true}
423            _ -> {:halt, false}
424          end
425        end)
426        |> elem(1)
427
428      true ->
429        false
430    end
431  end
432
433  @deprecated message
434  @spec to_list(t) :: list
435  def to_list(dict) do
436    target(dict).to_list(dict)
437  end
438
439  @spec unsupported_dict(t) :: no_return
440  defp unsupported_dict(dict) do
441    raise ArgumentError, "unsupported dict: #{inspect(dict)}"
442  end
443end
444