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.

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
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
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New

We're in Beta

About us Mission Statement