Difference in behavior between Enum.take_while/2 and Stream.take_while/2?

We had a recent refactoring case that led us to wonder what the difference between Enum.take_while/2 and Stream.take_while/2 is.

Here is a nice explanation of how Enum functions (the example was Enum.reduce_while/2) don’t actually hold the whole collection in the memory unless the final output of the function contains it.

So it seems to us that unless the take_while/2 is called in the middle of a chain of transformations on a stream (instead of as the last transformation), the Enum version and the Stream version should actually behave the same? Or maybe this understanding is not correct.

They will behave the same but Enum.take_while will return the computed value instead of a Stream that does nothing on its own.

3 Likes

As you’ve noted, the difference is important if there are further steps after take_while that don’t need all the values. For instance:

iex> stream = Stream.iterate(0, fn i -> IO.puts("Generating #{i+1}"); i + 1; end)

iex> stream |> Stream.take_while(& &1 < 10) |> Enum.take(4)                      
Generating 1
Generating 2
Generating 3
[0, 1, 2, 3]

iex> stream |> Enum.take_while(& &1 < 10) |> Enum.take(4)  
Generating 1
Generating 2
Generating 3
Generating 4
Generating 5
Generating 6
Generating 7
Generating 8
Generating 9
Generating 10
[0, 1, 2, 3]

If Stream.take_while is immediately followed by a non-lazy function that needs all the stream values (like Enum.to_list) then there shouldn’t be any notable difference.

4 Likes