Dedicated type for Stream - Why I think a stream is not an enumerable

As an example I picked a random function in the Stream module

  @spec chunk_every(Enumerable.t(), pos_integer) :: Enumerable.t()
  def chunk_every(enum, count), do: chunk_every(enum, count, count, [])

Returns an Enumerable, which is technically true, but streams are different in many ways from the data we handle in Enum, for example:

# Direcly related to the line in the Elixir source code I linked to

iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]

iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 2)
#Stream<[
  enum: [1, 2, 3, 4, 5, 6],
  funs: [#Function<3.58486609/1 in Stream.chunk_while/4>]
]>

The fact that inspect returns different results is a strong indicator to me that it is not the same.

Here is a more practice oriented example:

defmodule Printer do
  def print([]), do: IO.puts("End of Input")

  def print([something | rest]) do
    IO.inspect(something)
   print(rest)
  end
end

iex> "asdf,11111,0000,last" |> String.split(",") |> Printer.print()
"asdf"
"11111"
"0000"
"last"
End of Input
:ok

iex> "asdf,11111,0000,last" |> String.splitter(",") |> Printer.print()
** (FunctionClauseError) no function clause matching in Printer.print/1

iex> "asdf,11111,0000,last" |> String.splitter(",") |> Enum.each(&IO.inspect/1)
"asdf"
"11111"
"0000"
"last"
:ok

Streams and Enumerables behave the same in many cases, and the Enum module knows how to treat streams, but they are (for practical purposes) not the same. Stream even has its own module to deal with streams, or convert enumerables into a stream, and vice versa Enum takes streams and converts them into enumerables.

Definition of enumerable

able to be counted by one-to-one correspondence with the set of all positive integers.

The definition of a stream is that it cannot be determinate, because as soon as it becomes determinate (as for example in reduce), it stops being a stream - hence a stream cannot be counted, because as soon as you know a stream has x members, you converted it into an enumerable. Follows that a stream is not an enumerable.

Maybe I am missing something important here, please let me know if I do, but everything I know about streams and enumerables points to their functions should have returns with different types.

EDIT: I feel like I was just ranting - but what I actually wanted was either an explanation of why it’s not possible, or a prompt to open an issue on the Elixir source code.

EDIT2: I get that implementing protocols is different from the type now - I am still not sure why the typespec is Enumerable though. Easier for the implementation?

EDIT3: Now I get it… duck typing. It still feels wrong to return Enumerable for a (by definition) non enumerable.

That function does not work with streams but does neither work for all enumerable types. It only accepts lists.

For instance, maps are enumerables:

iex(4)> Enum.chunk_every(%{a: 1, b: 2, c: 3}, 2)  
[[a: 1, b: 2], [c: 3]]

If your Printer module should work with any enumerable, it should use Enum.map/2 or Enum.each/2 instead of pattern matching on lists recursively. And that would work with streams too.

1 Like

Thanks, that actually cleared up a lot for me!

Streams implement the enumerable protocol, but they are not enumerable.

iex>  i %{a: "a"}
Term
  %{a: "a"}
Data type
  Map
Reference modules
  Map
Implemented protocols

iex> i Stream.map(%{a: "a"}, & &1)
Term
  #Stream<[enum: %{a: "a"}, funs: [#Function<47.58486609/1 in Stream.map/2>]]>
Data type
  Stream
Description
  This is a struct. Structs are maps with a __struct__ key.
Reference modules
  Stream, Map
Implemented protocols
  Enumerable, IEx.Info, Inspect

I was pretty sure I must be missing something - but I couldn’t figure it out. Again, thanks :slight_smile:

1 Like

How are they not?

Because by definition, they are literally not enumerable. You cannot count them, unless you convert them into an enumerable.

They are more like… I can’t think of a good word here… I keep thinking about “traversable”, but that’s not right, because that would indicate you can have trees, or nodes etc., right?

They are countable.

There are enough natural numbers to give each element in a stream an index.

They are not countable until they stop being a stream.

I don’t want to sound condescending, but here is an analogy:

enumerable: I give you a basket full of apples. I ask you to count them. There are 10 apples. Countable.

stream: I give you one apple. And another one, and another one, and another one. I ask you how many I am going to give you. You have no idea until I tell you “that’s it”. Uncountable.

Here’s my analogy. That is “counted”. Countable means that it is possible to count, not that it has already happened in the past.

4 Likes

Not sure if you are contradicting, or backing me :slight_smile:

Even the latter example is countable, as for each apple you increase the current count by one.

Being countable does not mean “finite”.

The set of natural numbers is countable.

The set of integers is countable, as there is a technique to map one natural number to each integer. 0 is zero, positive integers are n * 2, negative integers are mapped to 1 - (n * 2). Or something like that, I don’t remember exactly.

The set of rationals isn’t, as you can’t clearly map a single natural number to a single rational number.

Math differs between a finite countable, infinite countable and infinite uncountable set.

1 Like

Contradicting.

Good point, and I admit I was arguing the wrong point.

Streams are enumerable, so are lists, maps etc. The difference is that streams are not finite, until they are. That makes streams different from other data structures implementing the enumerable protocol! Thanks for helping me with that :slight_smile:

I gotta be honest, I did not know why exactly it was bugging me so much, but I feel like I am getting a stronger case by gaining some insights.

2 Likes

The set of rationals isn’t, as you can’t clearly map a single natural number to a single rational number.

I think you are confusing rational numbers with real numbers. Rational numbers are perfectly countable, real numbers aren’t

2 Likes

original post removed - point already made above

Might be, I’m always confusing both, I’m talking about pi and sqrt(2) etc.

Though I also do not remember the mapping for n/m

natural: 0, 1, 2, 3, …
rational: n/m where n and m both natural numbers
real: e, pi, sqrt(2)

2 Likes

pi and sqrt(2) are irrational numbers, those are indeed uncountable.

Rational numbers are fractions: a/b.

The mapping between natural and rational numbers (proving that they are countable) is actually easier than one might think and kind of cool: An easy proof that rational numbers are countable

3 Likes