Last week I wrote about Stream patterns in Elixir based on examples I found throughout the Elixir project. I also mentioned that I was having trouble finding good examples of
Stream usage. This week I was reading through Chris McCord’s Metaprogramming Elixir. So far it has been a great read and I’ve learned a lot about macros in Elixir. And, more relevant to this post, I found a great
Stream example which I would like to analyze and develop into a pattern in this post.
Comprehension over Stream.cycle
Chapter 2 of Metaprogramming Elixir builds a macro to implement a while loop construct. The book builds up the example over several interations so its first version, shown here, is incomplete but illustrative:
This stream pattern boils down to
The pattern gives us a way to repeat the same function over and over again. The pattern I’ve shown is really equivalent to
Stream.repeatedly(f). The elements of the list,
[:ok] in the book, are unused within the do block and the function
f. We could ask ourselves, why not just use
Stream.repeatedly(f) here in this case? I believe, the example used in the book was choosen to be more illustrative - the block is in the for comprehension rather than an agrument to
Stream.repeatedly/1. This makes the example easier to follow.
But, in addition to clarity, this pattern can lead us to another. Generalizing, we can expand this pattern’s utility.
Comprehension over an infinite stream
We can generalize the pattern to
That is, an infinite stream can be used as the source of a for comprehension. This allows us to write an infinite loop, or more accurately an infinite for comprehension. Depdending on the situation this infinite comprehension could be useful.
One example to consider would be a server process which is usually written as an infinite recursion. We can use the pattern to implement something like a loop calling
receive over and over again in order to receive messages from other processes. Let’s consider a simple echo server and compare examples of the recursive and comprehension versions:
Using the comprehension of an infinite stream pattern we could rewrite the server as:
The infinite comprehension version required writing an anonymous function around
receive is not a function itself and this makes the code a bit longer. But, the major difference is that there isn’t a really clean way to exit from
receive_stream. The recursive solution gives a cleaner path to exiting - by simply not making the recursive call. For example we could rewrite the code like this:
Now, sending the server
:exit will cause it to exit. Chapter 2 in Metaprogramming Elixir gives a solution for exiting from a for comprehension. But in some server situations there is never a reason to exit. This comes down to a matter of style: which code do you prefer to read?
If you inerested in the book then sign up below to follow its progress and be notified when it is launched. You'll also receive two free chapters as a sample of what the book will contain.
Along the way you will also receive Elixir tips based on my research for the book.
This week we looked at another Stream example and generalized it to drive an infinite for comprehension. I worked through an example of a server process and wrote it using both the infinite for comprehension format as well as using the more traditional recursive function. The two forms are essentially equivalent, and I think the real difference is only a matter of style. And, as Chris McCord showed in Metaprogramming Elixir the infinite for comprehension style is more readable and illustrative in some situations.