Skip to content

Commit e499985

Browse files
author
José Valim
committed
Change Enumerable.count/1 and Enumerable.member?/2 to return tagged tuples
The motivation behind this change is to not force every application to implement count/1 and member?/2 manually. It also allows us in the future to add functions like Enum.counted?/1 that will return true if count is pre-calculated.
1 parent 33c6eb3 commit e499985

File tree

5 files changed

+113
-85
lines changed

5 files changed

+113
-85
lines changed

lib/elixir/lib/enum.ex

Lines changed: 101 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,20 @@ defprotocol Enumerable do
111111
@doc """
112112
Checks if a value exists within the collection.
113113
114-
Membership should be tested with the match (`===`) operator.
114+
It should return `{ :ok, boolean }` if membership can be tested
115+
faster than linear time with the match (`===`) operator, otherwise
116+
should return `{ :error, __MODULE__ }`.
115117
"""
116-
@spec member?(t, term) :: boolean
118+
@spec member?(t, term) :: { :ok, boolean } | { :error, module }
117119
def member?(collection, value)
118120

119121
@doc """
120122
Retrieves the collection's size.
123+
124+
Should return `{ :ok, size }` if the size is pre-calculated,
125+
`{ :error, __MODULE__ }` otherwise.
121126
"""
122-
@spec count(t) :: non_neg_integer
127+
@spec count(t) :: { :ok, non_neg_integer } | { :error, module }
123128
def count(collection)
124129
end
125130

@@ -160,56 +165,6 @@ defmodule Enum do
160165
@type index :: non_neg_integer
161166
@type default :: any
162167

163-
@doc """
164-
Checks if `value` exists within the `collection`.
165-
166-
Membership is tested with the match (`===`) operator, although
167-
enumerables like ranges may include floats inside the given
168-
range.
169-
170-
## Examples
171-
172-
iex> Enum.member?(1..10, 5)
173-
true
174-
iex> Enum.member?([:a, :b, :c], :d)
175-
false
176-
177-
"""
178-
@spec member?(t, element) :: boolean
179-
def member?(collection, value) do
180-
Enumerable.member?(collection, value)
181-
end
182-
183-
@doc """
184-
Returns the collection's size.
185-
186-
## Examples
187-
188-
iex> Enum.count([1, 2, 3])
189-
3
190-
191-
"""
192-
@spec count(t) :: non_neg_integer
193-
def count(collection) do
194-
Enumerable.count(collection)
195-
end
196-
197-
@doc """
198-
Returns the count of items in the collection for which
199-
`fun` returns `true`.
200-
201-
## Examples
202-
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
203-
2
204-
205-
"""
206-
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
207-
def count(collection, fun) do
208-
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
209-
{ :cont, if(fun.(entry), do: acc + 1, else: acc) }
210-
end) |> elem(1)
211-
end
212-
213168
@doc """
214169
Invokes the given `fun` for each item in the `collection` and returns `false`
215170
if at least one invocation returns `false`. Otherwise returns `true`.
@@ -352,6 +307,52 @@ defmodule Enum do
352307
reduce(enumerable, [], &reduce(&1, &2, fun)) |> :lists.reverse
353308
end
354309

310+
@doc """
311+
Returns the collection's size.
312+
313+
## Examples
314+
315+
iex> Enum.count([1, 2, 3])
316+
3
317+
318+
"""
319+
@spec count(t) :: non_neg_integer
320+
def count(collection) when is_list(collection) do
321+
:erlang.length(collection)
322+
end
323+
324+
def count(collection) do
325+
case Enumerable.count(collection) do
326+
value when is_integer(value) ->
327+
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.count/1 to return " <>
328+
"{ :ok, boolean } if pre-calculated, otherwise { :error, module }, got " <>
329+
"an integer\n#{Exception.format_stacktrace}"
330+
value
331+
{ :ok, value } when is_integer(value) ->
332+
value
333+
{ :error, module } ->
334+
module.reduce(collection, { :cont, 0 }, fn
335+
_, acc -> { :cont, acc + 1 }
336+
end) |> elem(1)
337+
end
338+
end
339+
340+
@doc """
341+
Returns the count of items in the collection for which
342+
`fun` returns `true`.
343+
344+
## Examples
345+
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
346+
2
347+
348+
"""
349+
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
350+
def count(collection, fun) do
351+
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
352+
{ :cont, if(fun.(entry), do: acc + 1, else: acc) }
353+
end) |> elem(1)
354+
end
355+
355356
@doc """
356357
Drops the first `count` items from `collection`.
357358
@@ -841,6 +842,43 @@ defmodule Enum do
841842
{ :lists.reverse(list), acc }
842843
end
843844

845+
@doc """
846+
Checks if `value` exists within the `collection`.
847+
848+
Membership is tested with the match (`===`) operator, although
849+
enumerables like ranges may include floats inside the given
850+
range.
851+
852+
## Examples
853+
854+
iex> Enum.member?(1..10, 5)
855+
true
856+
iex> Enum.member?([:a, :b, :c], :d)
857+
false
858+
859+
"""
860+
@spec member?(t, element) :: boolean
861+
def member?(collection, value) when is_list(collection) do
862+
:lists.member(value, collection)
863+
end
864+
865+
def member?(collection, value) do
866+
case Enumerable.member?(collection, value) do
867+
value when is_boolean(value) ->
868+
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.member?/2 to return " <>
869+
"{ :ok, boolean } if faster than linear, otherwise { :error, __MODULE__ }, " <>
870+
"got a boolean\n#{Exception.format_stacktrace}"
871+
value
872+
{ :ok, value } when is_boolean(value) ->
873+
value
874+
{ :error, module } ->
875+
module.reduce(collection, { :cont, false }, fn
876+
^value, _ -> { :halt, true }
877+
_, _ -> { :cont, false }
878+
end) |> elem(1)
879+
end
880+
end
881+
844882
@doc """
845883
Partitions `collection` into two collections, where the first one contains elements
846884
for which `fun` returns a truthy value, and the second one -- for which `fun`
@@ -1952,25 +1990,17 @@ defimpl Enumerable, for: List do
19521990
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
19531991
def reduce([h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
19541992

1955-
def member?([], _), do: false
1956-
def member?(list, value), do: :lists.member(value, list)
1957-
1958-
def count(list), do: length(list)
1993+
def member?(_list, _value),
1994+
do: { :error, __MODULE__ }
1995+
def count(_list),
1996+
do: { :error, __MODULE__ }
19591997
end
19601998

19611999
defimpl Enumerable, for: Function do
1962-
def reduce(function, acc, fun) do
1963-
function.(acc, fun)
1964-
end
1965-
1966-
def member?(function, value) do
1967-
function.({ :cont, false }, fn
1968-
^value, _ -> { :halt, true }
1969-
_, _ -> { :cont, false }
1970-
end) |> elem(1)
1971-
end
1972-
1973-
def count(function) do
1974-
function.({ :cont, 0 }, fn(_, acc) -> { :cont, acc + 1 } end) |> elem(1)
1975-
end
2000+
def reduce(function, acc, fun),
2001+
do: function.(acc, fun)
2002+
def member?(_function, _value),
2003+
do: { :error, __MODULE__ }
2004+
def count(_function),
2005+
do: { :error, __MODULE__ }
19762006
end

lib/elixir/lib/hash_dict.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,9 @@ end
478478

479479
defimpl Enumerable, for: HashDict do
480480
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
481-
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
482-
def member?(_dict, _), do: false
483-
def count(dict), do: HashDict.size(dict)
481+
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
482+
def member?(_dict, _), do: { :ok, false }
483+
def count(dict), do: { :ok, HashDict.size(dict) }
484484
end
485485

486486
defimpl Access, for: HashDict do

lib/elixir/lib/hash_set.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,6 @@ end
505505

506506
defimpl Enumerable, for: HashSet do
507507
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
508-
def member?(set, v), do: HashSet.member?(set, v)
509-
def count(set), do: HashSet.size(set)
508+
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
509+
def count(set), do: { :ok, HashSet.size(set) }
510510
end

lib/elixir/lib/range.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ defimpl Enumerable, for: Range do
4343

4444
def member?(first .. last, value) do
4545
if first <= last do
46-
first <= value and value <= last
46+
{ :ok, first <= value and value <= last }
4747
else
48-
last <= value and value <= first
48+
{ :ok, last <= value and value <= first }
4949
end
5050
end
5151

5252
def count(first .. _ = range) do
53-
Range.Iterator.count(first, range)
53+
{ :ok, Range.Iterator.count(first, range) }
5454
end
5555
end
5656

lib/elixir/lib/stream.ex

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,12 @@ defmodule Stream do
9595
end)
9696
end
9797

98-
def count(lazy) do
99-
do_reduce(lazy, { :cont, 0 }, fn _, [acc] -> { :cont, [acc + 1] } end) |> elem(1)
98+
def count(_lazy) do
99+
{ :error, __MODULE__ }
100100
end
101101

102-
def member?(lazy, value) do
103-
do_reduce(lazy, { :cont, false }, fn(entry, _) ->
104-
if entry === value, do: { :halt, [true] }, else: { :cont, [false] }
105-
end) |> elem(1)
102+
def member?(_lazy, _value) do
103+
{ :error, __MODULE__ }
106104
end
107105

108106
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs], acc, fun) do

0 commit comments

Comments
 (0)