Skip to content

Commit 18a8272

Browse files
author
José Valim
committed
Improve contracts for Set
Similar to how it has been done to Dict, functions in the Set are polymorphic but Set implementations should not provide polymorphic functions. This reduces the duplication inside each set implementation and provides both strict and polymorphic interfaces.
1 parent 12dabed commit 18a8272

File tree

3 files changed

+121
-56
lines changed

3 files changed

+121
-56
lines changed

lib/elixir/lib/dict.ex

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ defmodule Dict do
264264
the functions in entries in `b` have higher precedence unless a
265265
function is given to resolve conflicts.
266266
267-
Notice this function is polymorphic as it can merge dicts of any
267+
Notice this function is polymorphic as it merges dicts of any
268268
type. Each dict implementation also provides a `merge` function,
269269
but they can only merge dicts of the same type.
270270
@@ -286,15 +286,15 @@ defmodule Dict do
286286
287287
"""
288288
@spec merge(t, t, (key, value, value -> value)) :: t
289-
def merge(a, b, fun // fn(_k, _v1, v2) -> v2 end) do
290-
a_target = target(a)
291-
b_target = target(b)
289+
def merge(dict1, dict2, fun // fn(_k, _v1, v2) -> v2 end) do
290+
target1 = target(dict1)
291+
target2 = target(dict2)
292292

293-
if a_target == b_target do
294-
a_target.merge(a, b, fun)
293+
if target1 == target2 do
294+
target1.merge(dict1, dict2, fun)
295295
else
296-
b_target.reduce(b, { :cont, a }, fn({ k, v }, acc) ->
297-
{ :cont, a_target.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
296+
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
297+
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
298298
end) |> elem(1)
299299
end
300300
end
@@ -455,7 +455,7 @@ defmodule Dict do
455455
@doc """
456456
Check if two dicts are equal using `===`.
457457
458-
Notice this function is polymorphic as it can merge dicts of any
458+
Notice this function is polymorphic as it compares dicts of any
459459
type. Each dict implementation also provides an `equal?` function,
460460
but they can only compare dicts of the same type.
461461
@@ -473,17 +473,17 @@ defmodule Dict do
473473
474474
"""
475475
@spec equal?(t, t) :: boolean
476-
def equal?(a, b) do
477-
a_target = target(a)
478-
b_target = target(b)
476+
def equal?(dict1, dict2) do
477+
target1 = target(dict1)
478+
target2 = target(dict2)
479479

480480
cond do
481-
a_target == b_target ->
482-
a_target.equal?(a, b)
481+
target1 == target2 ->
482+
target1.equal?(dict1, dict2)
483483

484-
a_target.size(a) == b_target.size(b) ->
485-
a_target.reduce(a, { :cont, true }, fn({ k, v }, _acc) ->
486-
case b_target.fetch(b, k) do
484+
target1.size(dict1) == target2.size(dict2) ->
485+
target1.reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
486+
case target2.fetch(dict2, k) do
487487
{ :ok, ^v } -> { :cont, true }
488488
_ -> { :halt, false }
489489
end

lib/elixir/lib/hash_set.ex

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,14 @@ defmodule HashSet do
7272
set_fold set2, set1, fn v, acc -> put(acc, v) end
7373
end
7474

75-
def union(trie() = set1, set2) do
76-
set_fold set1, set2, fn v, acc -> put(acc, v) end
77-
end
78-
7975
def intersection(trie() = set1, trie() = set2) do
8076
set_fold set1, trie(), fn v, acc ->
8177
if member?(set2, v), do: put(acc, v), else: acc
8278
end
8379
end
8480

85-
def intersection(trie() = set1, set2) do
86-
set_fold set1, trie(), fn v, acc ->
87-
if Set.member?(set2, v), do: put(acc, v), else: acc
88-
end
89-
end
90-
9181
def difference(trie() = set1, trie() = set2) do
92-
set_fold set1, trie(), fn v, acc ->
93-
if member?(set2, v), do: acc, else: put(acc, v)
94-
end
95-
end
96-
97-
def difference(trie() = set1, set2) do
98-
set_fold set1, trie(), fn v, acc ->
99-
if Set.member?(set2, v), do: acc, else: put(acc, v)
100-
end
82+
set_fold set2, set1, fn v, acc -> delete(acc, v) end
10183
end
10284

10385
def to_list(set) do
@@ -120,16 +102,7 @@ defmodule HashSet do
120102
end) |> elem(1)
121103
end
122104

123-
def subset?(trie() = set1, set2) do
124-
reduce(set1, { :cont, true }, fn member, acc ->
125-
case Set.member?(set2, member) do
126-
true -> { :cont, acc }
127-
_ -> { :halt, false }
128-
end
129-
end) |> elem(1)
130-
end
131-
132-
def disjoint?(set1, set2) do
105+
def disjoint?(trie() = set1, trie() = set2) do
133106
reduce(set2, { :cont, true }, fn member, acc ->
134107
case member?(set1, member) do
135108
false -> { :cont, acc }
@@ -147,14 +120,14 @@ defmodule HashSet do
147120
end
148121

149122
def put(trie(root: root, size: size), term) do
150-
{ root, counter } = do_put(root, term, key_hash(term))
123+
{root, counter} = do_put(root, term, key_hash(term))
151124
trie(root: root, size: size + counter)
152125
end
153126

154127
def delete(trie(root: root, size: size) = set, term) do
155128
case do_delete(root, term, key_hash(term)) do
156-
{ :ok, root } -> trie(root: root, size: size - 1)
157-
:error -> set
129+
{:ok, root} -> trie(root: root, size: size - 1)
130+
:error -> set
158131
end
159132
end
160133

lib/elixir/lib/set.ex

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ defmodule Set do
4949
defcallback intersection(t, t) :: t
5050
defcallback member?(t, value) :: boolean
5151
defcallback put(t, value) :: t
52+
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
5253
defcallback size(t) :: non_neg_integer
5354
defcallback subset?(t, t) :: boolean
5455
defcallback to_list(t) :: list()
@@ -85,6 +86,10 @@ defmodule Set do
8586
@doc """
8687
Returns a set that is `set1` without the members of `set2`.
8788
89+
Notice this function is polymorphic as it calculates the difference
90+
for of any type. Each set implementation also provides a `difference`
91+
function, but they can only work with sets of the same type.
92+
8893
## Examples
8994
9095
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
@@ -93,12 +98,25 @@ defmodule Set do
9398
"""
9499
@spec difference(t, t) :: t
95100
def difference(set1, set2) do
96-
target(set1).difference(set1, set2)
101+
target1 = target(set1)
102+
target2 = target(set2)
103+
104+
if target1 == target2 do
105+
target1.difference(set1, set2)
106+
else
107+
target2.reduce set2, { :cont, set1 }, fn v, acc ->
108+
{ :cont, target1.delete(acc, v) }
109+
end |> elem(1)
110+
end
97111
end
98112

99113
@doc """
100114
Checks if `set1` and `set2` have no members in common.
101115
116+
Notice this function is polymorphic as it checks for disjoint sets of
117+
any type. Each set implementation also provides a `disjoint?` function,
118+
but they can only work with sets of the same type.
119+
102120
## Examples
103121
104122
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
@@ -109,7 +127,19 @@ defmodule Set do
109127
"""
110128
@spec disjoint?(t, t) :: boolean
111129
def disjoint?(set1, set2) do
112-
target(set1).disjoint?(set1, set2)
130+
target1 = target(set1)
131+
target2 = target(set2)
132+
133+
if target1 == target2 do
134+
target1.disjoint?(set1, set2)
135+
else
136+
target2.reduce(set2, { :cont, true }, fn member, acc ->
137+
case target1.member?(set1, member) do
138+
false -> { :cont, acc }
139+
_ -> { :halt, false }
140+
end
141+
end) |> elem(1)
142+
end
113143
end
114144

115145
@doc """
@@ -121,7 +151,11 @@ defmodule Set do
121151
end
122152
123153
@doc """
124-
Checks if `set1` and `set2` are equal.
154+
Check if two sets are equal using `===`.
155+
156+
Notice this function is polymorphic as it compares sets of
157+
any type. Each set implementation also provides an `equal?`
158+
function, but they can only work with sets of the same type.
125159

126160
## Examples
127161

@@ -134,12 +168,28 @@ defmodule Set do
134168
"""
135169
@spec equal?(t, t) :: boolean
136170
def equal?(set1, set2) do
137-
target(set1).equal?(set1, set2)
171+
target1 = target(set1)
172+
target2 = target(set2)
173+
174+
cond do
175+
target1 == target2 ->
176+
target1.equal?(set1, set2)
177+
178+
target1.size(set1) == target2.size(set2) ->
179+
do_subset?(target1, target2, set1, set2)
180+
181+
true ->
182+
false
183+
end
138184
end
139185
140186
@doc """
141187
Returns a set containing only members in common between `set1` and `set2`.
142188
189+
Notice this function is polymorphic as it calculates the intersection of
190+
any type. Each set implementation also provides a `intersection` function,
191+
but they can only work with sets of the same type.
192+
143193
## Examples
144194
145195
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
@@ -151,7 +201,16 @@ defmodule Set do
151201
"""
152202
@spec intersection(t, t) :: t
153203
def intersection(set1, set2) do
154-
target(set1).intersection(set1, set2)
204+
target1 = target(set1)
205+
target2 = target(set2)
206+
207+
if target1 == target2 do
208+
target1.intersection(set1, set2)
209+
else
210+
target1.reduce set1, { :cont, target1.empty(set1) }, fn v, acc ->
211+
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
212+
end |> elem(1)
213+
end
155214
end
156215

157216
@doc """
@@ -205,6 +264,10 @@ defmodule Set do
205264
@doc """
206265
Checks if `set1`'s members are all contained in `set2`.
207266
267+
Notice this function is polymorphic as it checks the subset for
268+
any type. Each set implementation also provides a `subset?` function,
269+
but they can only work with sets of the same type.
270+
208271
## Examples
209272
210273
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
@@ -214,7 +277,14 @@ defmodule Set do
214277
"""
215278
@spec subset?(t, t) :: boolean
216279
def subset?(set1, set2) do
217-
target(set1).subset?(set1, set2)
280+
target1 = target(set1)
281+
target2 = target(set2)
282+
283+
if target1 == target2 do
284+
target1.subset?(set1, set2)
285+
else
286+
do_subset?(target1, target2, set1, set2)
287+
end
218288
end
219289

220290
@doc """
@@ -234,6 +304,10 @@ defmodule Set do
234304
@doc """
235305
Returns a set containing all members of `set1` and `set2`.
236306
307+
Notice this function is polymorphic as it calculates the union of
308+
any type. Each set implementation also provides a `union` function,
309+
but they can only work with sets of the same type.
310+
237311
## Examples
238312
239313
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
@@ -242,7 +316,25 @@ defmodule Set do
242316
"""
243317
@spec union(t, t) :: t
244318
def union(set1, set2) do
245-
target(set1).union(set1, set2)
319+
target1 = target(set1)
320+
target2 = target(set2)
321+
322+
if target1 == target2 do
323+
target1.union(set1, set2)
324+
else
325+
target2.reduce set2, { :cont, set1 }, fn v, acc ->
326+
{ :cont, target1.put(acc, v) }
327+
end |> elem(1)
328+
end
329+
end
330+
331+
defp do_subset?(target1, target2, set1, set2) do
332+
target1.reduce(set1, { :cont, true }, fn member, acc ->
333+
case target2.member?(set2, member) do
334+
true -> { :cont, acc }
335+
_ -> { :halt, false }
336+
end
337+
end) |> elem(1)
246338
end
247339

248340
defp unsupported_set(set) do

0 commit comments

Comments
 (0)