Skip to content

Commit fb49aa1

Browse files
author
José Valim
committed
Deprecate Dict.Behaviour in favor of Dict
1 parent a6b9d2e commit fb49aa1

File tree

6 files changed

+215
-193
lines changed

6 files changed

+215
-193
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* [OptionParser] `--` is always removed from argument list on `parse/2` and when it is the leading entry on `parse_head/2`
1616

1717
* Soft deprecations (no warnings emitted)
18-
* [Dict] `Dict.Behaviour` is deprecated (please define the Dict functions yourself)
18+
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
1919
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
2020
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
2121
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`

lib/elixir/lib/dict.ex

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,70 @@ defmodule Dict do
3131
3232
Dictionaries are required to implement all operations
3333
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+
3498
"""
3599

36100
use Behaviour
@@ -63,6 +127,146 @@ defmodule Dict do
63127
defcallback update!(t, key, (value -> value)) :: t | no_return
64128
defcallback values(t) :: list(value)
65129

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+
66270
defmacrop target(dict) do
67271
quote do
68272
case unquote(dict) do

lib/elixir/lib/dict/behaviour.ex

Lines changed: 1 addition & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,133 +2,8 @@ defmodule Dict.Behaviour do
22
@moduledoc false
33

44
defmacro __using__(_) do
5-
# Use this import to guarantee proper code expansion
6-
import Kernel, except: [size: 1]
7-
85
quote do
9-
@behaviour Dict
10-
11-
def get(dict, key, default \\ nil) do
12-
case fetch(dict, key) do
13-
{:ok, value} -> value
14-
:error -> default
15-
end
16-
end
17-
18-
def fetch!(dict, key) do
19-
case fetch(dict, key) do
20-
{:ok, value} -> value
21-
:error -> raise KeyError, key: key, term: dict
22-
end
23-
end
24-
25-
def has_key?(dict, key) do
26-
match? {:ok, _}, fetch(dict, key)
27-
end
28-
29-
def put_new(dict, key, value) do
30-
update(dict, key, value, fn(v) -> v end)
31-
end
32-
33-
def drop(dict, keys) do
34-
Enum.reduce keys, dict, &delete(&2, &1)
35-
end
36-
37-
def take(dict, keys) do
38-
Enum.reduce keys, new, fn key, acc ->
39-
case fetch(dict, key) do
40-
{:ok, value} -> put(acc, key, value)
41-
:error -> acc
42-
end
43-
end
44-
end
45-
46-
def to_list(dict) do
47-
reduce(dict, {:cont, []}, fn
48-
kv, acc -> {:cont, [kv|acc]}
49-
end) |> elem(1) |> :lists.reverse
50-
end
51-
52-
def keys(dict) do
53-
reduce(dict, {:cont, []}, fn
54-
{k, _}, acc -> {:cont, [k|acc]}
55-
end) |> elem(1) |> :lists.reverse
56-
end
57-
58-
def values(dict) do
59-
reduce(dict, {:cont, []}, fn
60-
{_, v}, acc -> {:cont, [v|acc]}
61-
end) |> elem(1) |> :lists.reverse
62-
end
63-
64-
def equal?(dict1, dict2) do
65-
# Use this import to avoid conflicts in the user code
66-
import Kernel, except: [size: 1]
67-
68-
case size(dict1) == size(dict2) do
69-
false -> false
70-
true ->
71-
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
72-
case fetch(dict2, k) do
73-
{:ok, ^v} -> {:cont, true}
74-
_ -> {:halt, false}
75-
end
76-
end) |> elem(1)
77-
end
78-
end
79-
80-
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
81-
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
82-
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
83-
end) |> elem(1)
84-
end
85-
86-
def update(dict, key, initial, fun) do
87-
case fetch(dict, key) do
88-
{ :ok, value } ->
89-
put(dict, key, fun.(value))
90-
91-
:error ->
92-
put(dict, key, initial)
93-
end
94-
end
95-
96-
def update!(dict, key, fun) do
97-
case fetch(dict, key) do
98-
{ :ok, value } ->
99-
put(dict, key, fun.(value))
100-
101-
:error ->
102-
raise KeyError, key: key, term: dict
103-
end
104-
end
105-
106-
def pop(dict, key, default \\ nil) do
107-
case fetch(dict, key) do
108-
{ :ok, value } ->
109-
{ value, delete(dict, key) }
110-
111-
:error ->
112-
{ default, dict }
113-
end
114-
end
115-
116-
def split(dict, keys) do
117-
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
118-
case fetch(exc, key) do
119-
{ :ok, value } ->
120-
{ put(inc, key, value), delete(exc, key) }
121-
122-
:error ->
123-
acc
124-
end
125-
end
126-
end
127-
128-
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
129-
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
130-
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
131-
split: 2
6+
use Dict
1327
end
1338
end
1349
end

0 commit comments

Comments
 (0)