ChrisCoy

ChrisCoy

Questions about Streams

Hi everyone, hope y’all doing well, I am quite new to elixir and functional programming in general and I never really used Streams in another laguange before, so I have some questions about Streams.

file = File.stream!("elixir_is_funny.txt")
file |> Enum.map(&foo/1) |> Enum.map(&foo/2) |> Enum.map(&/foo3) |> Enum.take(1)

  • each Enum.map will it execute just one time? If yes, how? how does it knows that i will take only one?

  • I’ve read that in order to Enum works, you have to implement a iterable in the module, how that works with streams?

  • I’ve made some tests, and in some cases it stops being a stream, how can i work with streams without transforming it?

  • I’ve heard about the term lazy load, what does that mean? is Streams lazy loaded?

Well thats it, sorry for my english, it is not my first language :smiley:

Most Liked

LostKobrakai

LostKobrakai

Before you can truely understand streams in elixir you need to understand enumerables.

Enumerable is a protocol for collections of data, which can be iterated over using reduce at its core. An enumerable can be e.g. a list, which already exists completely in memory ready to be iterated over. It can however also be “lazy” in that the actual values the collection exists of are only computed or pulled into memory once the enumerable is iterated / reduced over.

A simple case for a lazy enumerable in elixir is Range. It models a sequence of interger values, but the struct only stores the lower and upper boundary of the sequence. 2..15 only stores a struct containing 2 and 15. Only once you start iterating over the range the values between those two numbers are computed – so 3, 4, 5, ….

So that’s Enumerable. Now there’s also Enum and Stream.

Enum provides APIs to work with Enumerable data. It is eager in the sense that each call to an Enum function will iterate over as much of the enumerable collection as the operation would require. E.g. Enum.map will iterate over the complete input and return a list of mapped values for each item in the input collection. Enum.take(input, 1) will take out the first item from the input and be done.

The return value of Enum apis might be another Enumerable, which is why you can often see chained calls to its apis, but it’ll never be another lazy enumerable.

Now the difference with Stream is that its apis will always return lazy enumerables. So the input is wrapped with some lazily computable operation, which gives you a lazy enumerable. Some functions don’t even require an enumerable as input, but build an lazy enumerable from other input, others wrap existing enumerables – them being lazy or not.

Therefore the answers to your questions:

Yes, each Enum.map will execute once, but not just for a single element of your initial input. They’ll all map over all lines of your file and only the last step will discard all the work, besides for the first item in there. Previous steps have no idea you’ll discard stuff in the end.

See above. The protocol is Enumerable and the way it works does allow for both lazy and “not so lazy” enumerables.

There’s no “stream” datatype and also things on the beam are immutable. So there’s no way to change something without changing it. But I hope I explained enough before that streams don’t just stop being streams. If you want to retain an lazy enumerables lazyness you add additional operations using Stream apis. Once you start using Enum operations become eager.

Lazy really only refers to the fact that items of an enumerable collection somehow become available “on demand”. This could be being loaded on demand or computed, …

Aetherus

Aetherus

Let’s do an experiment.

  1. Create a text file with the name nums.txt and the following content:
    1
    2
    3
    
  2. Open iex
  3. Try File.stream!("nums.txt") |> Enum.map(&IO.inspect/1) |> Enum.map(&IO.inspect/1) |> Enum.take(1), what do you see?
  4. Change Enum.map to Stream.map, what do you see?
  5. Change Enum.take(1) to Enum.take(2), what do you see?
Aetherus

Aetherus

This is what I always think of Stream and Enum.

something_enumerable  # <-- upmost stream
|> Stream.do_something(...)
|> Enum.do_something(...)
|> Enum.do_something(...)  # <-- downmost stream

Enum

When you call Enum.do_something, it always tells its upstream “give me everything you have, NOW!”, then it iterates through all the things it gets and does some work to them, then returns something solid (like a list or a map).

Stream

When you call Stream.do_something, it tells the upstream “I’ll ask you for a value whenever I’m asked for a value. Other times I just sit there doing nothing.” So, if there’s no need for a value, it does nothing. The return value of Stream.do_something is not that solid.

Common functions

If some job can be done in a per-value way, like transforming a value to another value based on that value only, then such functions can be found in both Stream and Enum, like map and filter.

Enum-only functions

If something can only be done considering the whole Enumerable, then such functions can only be found in Enum, like sort and group_by and reduce.

Stream-only functions

Because Stream is lazy, it has the potential of generating an infinite number of values. For example, Stream.repeatedly(&:rand.uniform/0) can generate an infinite number of random float numbers, not at once, but whenever you need a float. Such “value generating functions” are only found in Stream.

ChrisCoy

ChrisCoy

wow thats cool, by using Stream.map it only runs each IO.inspect once.

Thank you very much for this practical example, really simple and didatic.

ChrisCoy

ChrisCoy

Now it makes sense to me, i was thinking that by using Enum methods on a lazy input it somehow continued to output a stream. In fact it’s away more simpler than i thought.

Thank you so much @LostKobrakai, really appreciated your explanation.

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement