Skip to content

What Makes Elixir Streams Composable

Published: at 07:45 PM

Streams in Elixir are lazy enumerables, making them composable. Instead of executing immediately, they act as receipts describing the computation that is to be evaluated, on demand, at a later time. In order to execute the computation, you must pass the stream to a function from the Enum module such as each, map or filter.

In comparison, functions from the Enum module are eager, and their computations are evaluated and executed straight away.

The following code examples are copied from the Elixir documentation and does a great job of illustrating how eager and lazy enumerables compare to each other.

Eager example:

1..3
  |> Enum.map(&IO.inspect(&1))
  |> Enum.map(&(&1 * 2))
  |> Enum.map(&IO.inspect(&1))
1
2
3
2
4
6
#=> [2, 4, 6]

Lazy example:

stream =
  1..3
  |> Stream.map(&IO.inspect(&1))
  |> Stream.map(&(&1 * 2))
  |> Stream.map(&IO.inspect(&1))
Enum.to_list(stream)
1
2
2
4
3
6
#=> [2, 4, 6]

Both of the two examples above return the same result, but the order in which the numbers are printed is different. In the first example, each map is doing a complete sweap through all element before continuing to the next pipe. In the second example using a stream, the iteration doesn’t begin until the stream is sent to Enum.to_list/1, which converts any kind of enumerable to a list. It also produces the values one by one.

Because the stream iteration deals with one element at a time, it is possible to use Enum.take/2 and only ask for one element from the stream. The other elements are never visited. Here is an example of doing just that:

stream =
  1..3
  |> Stream.map(&IO.inspect(&1))
  |> Stream.map(&(&1 * 2))
  |> Stream.map(&IO.inspect(&1))

Enum.take(stream, 1)

A typical scenario where using a stream makes sense is when you have a program that needs to parse every line of a file. With a stream, your program won’t have to keep the entire file in memory, allowing you to work on each line individually. The laziness of streams therefore enable effective ways of working with large and potentially slow data sets.