Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News Streams Library Brings Lazy Evaluation and Functional-style to C++14

Streams Library Brings Lazy Evaluation and Functional-style to C++14

This item in japanese

Streams is a C++14 library that provides lazy evaluation and functional-style transformations on the data, to ease the use of C++ standard library containers and algorithms. Streams support many common functional operations such as map, filter, and reduce.

Streams are an abstraction on a set of data that has a specific order. Various operations can be applied to streams such that data passes through the stream pipeline in a lazy manner, only getting passed to the next operation when it is requested. There are 3 main kinds of stream operators:

  • Generators are methods that create streams, and are thus considered stream sources.
  • Intermediate operators take in streams and return streams, applying some additional operation to the end of the stream pipeline.
  • Terminal operators close a stream and finally cause all of the stream operations to evaluate up the stream pipeline.

Both intermediate operations and generators will not be evaluated until some terminal operation on the stream has been called.

An example for summing the first ten squares is:

int total = stream::MakeStream::counter(1)
    .map([] (int x) { return x * x; })

This is how to extract values from objects and handling them through a set:

std::vector widgets = /* ... */    
std::set ids = stream::MakeStream::from(widgets)

Besides functional operations such as map, filter, and reduce, Streams supports other useful operations such as various set operations (union, intersection, difference), partial sum, and adjacent difference, and others.

InfoQ has got in touch with Streams' author, Jonah Scheinerman, to ask him a few questions about his work.

What were the main reasons that led you to create Streams?

In C++ there are some standard library tools and algorithms that are fantastic, but have too much syntactic overhead making them lose their expressive power. For example, consider having to do a set intersection:
std::set left = /* ... */
std::set right = /* ... */
std::set result; std::set_intersection(left.begin(), left.end(),
                         right.begin(), right.end(),
                         std::inserter(result, result.end())); 
There is so much overhead here that the idea that you want to do a set intersection between left and right sets and save it as a set is lost. Wouldn't it be nicer if you could just express it simply:
stream::Stream left = /* ... */  
stream::Stream right = /* ... */  
std::set result = left.intersect_with(right).as_set();
These kinds of cases have guided the design of streams. Many other standard library algorithms suffer from the excessive use of calls to begin() and end(), as well as use of annoying wrappers like std::inserter and std::back_inserter. Additionally, if you are performing a series of transformations on a set of data, this becomes even more obnoxious, requiring you to create many temporary vectors of data are only used in the next step of your calculation. Though the standard library functions are incredibly versatile, in the overwhelmingly common cases they are far too verbose. The benefits of C++11 are lost in the standard library features which were designed for a world without move semantics.
A large catalyst for working on this was the introduction of Java 8 Streams (with inspiration from Python's itertools as well). If Java can have lazy evaluation, why shouldn't C++ be able to have something even better?

How would you describe Streams strengths and advantages so that a developer can make the choice of using it instead of any existing alternatives that also try to bring functional style and lazy evaluation to C++ (e.g. Boost.Lambda, Boost.Phoenix, etc.)?

Streams main advantages lie in its use of modern language features and its syntactic simplicity. Boost.Phoenix is certainly interesting but it feels like an incredibly hacky work around to a pre-lambda C++. Its use of operator overloads and functions named similarly to keywords (e.g. for_ instead of for) make it feel awkward to use. Boost.Lambda offers no benefits since C++11.
The only current competitor to Streams that I am aware of is Reactive Expressions (Rx) which has slightly different goals than Streams does (Rx is designed for concurrency, Streams for syntax and readability). However, I think that having Rx as a competitor will be good for both of us, encouraging us to build out better features.

Where is Streams heading? Have you defined any roadmap for the library?

In the near future, I hope to clean up the code, make the library C++11 compatible, add benchmarks and tests, and expand upon the documentation. Further down the road, my goals include adding concurrency and making streams more flexible so that others can easily add their own stream operators. Ultimately, it would be awesome if lazy evaluation could find its way in to the C++ standard. Maybe it will look nothing like Streams, but one can always hope.

Streams is available on GitHub and released under the MIT License.

Rate this Article