A few catches on empty Enumerables

81 views
Skip to first unread message

eksperimental

unread,
Sep 25, 2015, 2:30:39 PM9/25/15
to elixir-l...@googlegroups.com
While creating a new Enum.pad and Enum.pad_zip function, and
writting the tests I came accross a few issues dealing with empty
enumerables, that I think it's worth to look at.

1) On empty ranges
Should we have empty ranges?
Well, we do have them somehow.

iex> %Range{}
nil..nil
iex> %Range{first: nil, last: nil}
nil..nil
iex> Range.new(nil, nil)
nil..nil
iex> %Range{} == Range.new(nil, nil)
true
iex> Range.new(nil, nil) == nil..nil
** (ArgumentError) ranges (left .. right) expect both sides to be
integers, got: nil .. nil
(elixir) expanding macro: Kernel.../2
iex:52: (file)
iex> %Range{} == nil..nil
** (ArgumentError) ranges (left .. right) expect both sides to be
integers, got: nil .. nil
(elixir) expanding macro: Kernel.../2
iex:51: (file)

I think the message is wrong, saying that expects both sides to be an
integer, since nil..nil is a valid range.

I think Enumerable.Range.validate_range!/2 should be fixed to accept
nil..nil as a valid range.


*****************************************

2) Empty ranges should return 0 on Enum.count/1 and should return an
empty list when converted to a list

iex> %Range{}
nil..nil

iex> %Range{} |> Enum.count
** (ArgumentError) ranges (left .. right) expect both sides to be
integers, got: nil..nil (elixir) lib/range.ex:100:
Enumerable.Range.validate_range!/2 (elixir) lib/range.ex:90:
Enumerable.Range.count/1 (elixir) lib/enum.ex:437: Enum.count/1

iex> %Range{} |> Enum.to_list
** (ArgumentError) ranges (left .. right) expect both sides to be
integers, got: nil..nil
(elixir) lib/range.ex:100: Enumerable.Range.validate_range!/2
(elixir) lib/range.ex:56: Enumerable.Range.reduce/3
(elixir) lib/enum.ex:1389: Enum.reduce/3
(elixir) lib/enum.ex:2128:
Enum.to_list/1

# Same when we want to take an element
iex(49)> %{} |> Enum.take(1)
[]
iex(50)> %Range{} |> Enum.take(1)
** (ArgumentError) ranges (left .. right) expect both sides to be
integers, got: nil..nil
(elixir) lib/range.ex:100: Enumerable.Range.validate_range!/2
(elixir) lib/range.ex:56: Enumerable.Range.reduce/3
(elixir) lib/enum.ex:1948: Enum.take/2

*****************************************

3) Stream.cycle/1 : cycling over an empty list should return an
empty list.

iex(42)> [:a] |> Enum.take(1)
[:a]
iex(43)> [:a] |> Enum.take(0)
[]
iex(44)> [] |> Enum.take(1)
[]
iex(45)> [] |> Enum.take(0)
[]
iex(46)> Stream.cycle([:a]) |> Enum.take(1)
[:a]
iex(47)> Stream.cycle([:a]) |> Enum.take(0)
[]
iex(48)> Stream.cycle([]) |> Enum.take(1)
** (FunctionClauseError) no function clause matching in anonymous fn/1
in Stream.cycle/1
(elixir) lib/stream.ex:920: anonymous fn({[], []}) in
Stream.cycle/1
(elixir) lib/stream.ex:1152: Stream.do_unfold/4
(elixir) lib/enum.ex:1948: Enum.take/2
iex(48)> Stream.cycle([]) |> Enum.take(0)
[]


# Here's a bug, it hangs endlessly when provided an empty map
iex(63)> Stream.cycle(%{}) |> Enum.take(1)

******************
It think the idea of empty ranges and streams, like in any other
enumerable makes sense.

Let me know what you guys think about it?
- eksperimental

Aleksei Magusev

unread,
Sep 25, 2015, 4:29:50 PM9/25/15
to elixir-lang-core, eksper...@autistici.org
1) On empty ranges 

No one should ever manually create ranges using struct syntax %Range{}.
If you do - you are the only one responsible for possible failures.
nil..nil isn't a valid range. I think Range.new/2 should have same guards as Kernel.../2 has.

eksperimental

unread,
Sep 26, 2015, 2:32:35 AM9/26/15
to elixir-l...@googlegroups.com
On Fri, 25 Sep 2015 13:29:49 -0700 (PDT)
Aleksei Magusev <lex...@gmail.com> wrote:
> No one should ever manually create ranges using struct syntax
> %Range{}. If you do - you are the only one responsible for possible
> failures. nil..nil isn't a valid range. I think Range.new/2 should
> have same guards as Kernel.../2 has.

Then in that case, I think —as you said— we should add some guard to
Range.new/2 and also update the specs in the Range module. Right now
first are last are set to any.

My question is why an empty range is not considered a valid one. The
rest of the enumerables, have an empty representation of them.

José Valim

unread,
Sep 26, 2015, 5:00:59 AM9/26/15
to elixir-l...@googlegroups.com
 
Then in that case, I think —as you said— we should add some guard to
Range.new/2 and also update the specs in the Range module. Right now
first are last are set to any.

Yes, please .
 
My question is why an empty range is not considered a valid one. The
rest of the enumerables, have an empty representation of them.

That's not true. For example, Stream.repeatedly does not have an empty representation. Enumerables are not required to provide an empty representation.

eksperimental

unread,
Sep 26, 2015, 6:48:34 AM9/26/15
to elixir-l...@googlegroups.com
> > Then in that case, I think —as you said— we should add some guard to
> > Range.new/2 and also update the specs in the Range module. Right now
> > first are last are set to any.
> Yes, please .

Ok, I will submit a PR,

> > My question is why an empty range is not considered a valid one. The
> > rest of the enumerables, have an empty representation of them.
> That's not true. For example, Stream.repeatedly does not have an empty
> representation. Enumerables are not required to provide an empty
> representation.

So what about point #3.

José Valim

unread,
Sep 26, 2015, 7:03:29 AM9/26/15
to elixir-l...@googlegroups.com
3) Stream.cycle/1 : cycling over an empty list should return an
empty list.

The point of cycle is to restart the enumeration when it finishes. It has no idea about emptiness. It is working as designed.



José Valim
Skype: jv.ptec
Founder and Director of R&D


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/20150926174823.5e7507da.eksperimental%40autistici.org.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages