In this short article, I’ll lead you through a crash course on what a stream is, and how you can harness them to make your code more understandable.
If you want to try out any of the examples here, they will work with the excellent library bacon.js.
So what is a stream?
A stream is a sequence of objects, but unlike other sequences, it’s handled lazily (so the items aren’t retrieved until they’re required). This means that unlike a normal List, it doesn’t have to be finitely long.
You can put anything in your stream characters, numbers or even user events (like keystrokes or mouse presses). Once you’ve got your sequence, you can manipulate it in lots of different ways.
Streams can also completely remove the requirement to think about time, meaning fewer race conditions and therefore more declarative code. This removal of the need to represent time in code also results in architecture diagrams which are easy to draw and reason about.
Manipulating your stream
Now that we know the purpose of a stream, how do we manipulate it? What follows is a selection of functions that can be called on streams along with visual representations of the effect of these functions when called. It should provide everything needed to set up and use some programs which utilise streams.
Map takes a function f. Then, for every item that appears in the stream, it applies the function f to it and produces a new stream with the new values.
Scan takes a function f. Then for each value in the stream, applies the function to the previous result along with the current value. This is similar to fold, but the output is a stream of all the values rather than just the final value, which fold would provide.
Filter takes a function f, then for each value computes the function, and if it evaluates to true, puts the value into the output stream, else discards the value.
There are several ways to combine two streams. Using merge will take values from both streams and output values (in the order they are received) into the output stream.
This differs from concat, which takes two streams and returns all of the first stream before returning all of the second stream.
SampledBy takes a function f and two streams (A and B). The function f is then called whenever a value is provided by stream A, passing in the last value from each stream. In the example below, a value is created in the output stream whenever a value occurs in the top stream. The output is then created from the value in the bottom stream.
SlidingWindow takes an integer n and returns the last n values from the stream as an array each time a new value occurs in the input stream.
So in summary, you can use stream functions to manipulate streams in a multitude of different ways. You should use streams to remove the time component from code, allowing for simpler to understand and debug code.