Support Class Library
A set of tools providing classes and utility
|
The Support Class Library (or SCL, formerly named Standard Class Library as a nod to the STL) is a library built on top of the STL and wants to provide tools and interfaces for common problems and patterns in order to make application development easier.
The stream API is a library that allows lazy evaluation of potentially infinite sequences of data in an easy to write and easy to extend fashion.
For instance, a recurrent task is to filter through a collection and execute an action on the filtered elements, let's compare how one would approach this task in both pure STL and using scl::stream
.
We will assume that the data is initially stored in a std::vector
.
In this simple use-case, using a for-loop reduces the amount of unnecessary copies that the otherwise functional-ish code would make. But things can get quite messy with a for-loop : conditions in conditions, chains of conditions, etc... . With the stream API, we want to get the best of both world : expressive and as efficient as a single for-loop.
Here we get a lot of advantages :
filter
expresses more intent than copy_if
(because copy_if
was not the actual intent, we used it to filter and use the result afterward)streamFrom
is where we begin the sequence processing, |
indicate the start of a new operation, ;
ends the sequence processingThe library is greatly inspired by rangev3, Rust's iter
, JS functional methods for arrays, the JS library sequency
and Java 8's Stream API.
In addition to the looks of it, using free functions and operators instead of methods provide one really important selling point : extendability.
With methods, if you want to add an operation for streams you would have to :
scl::stream::Stream
scl::stream::Stream
One could argue that with some runtime polymorphism shenanigans provided by the base class we could achieve something similar but it would be painful for the SCL maintainers and probably for the library maintainers.
Using free functions, to add an operation, all you have to do is :
operator|
overload for your toolbox/type tag (if needed)The pain is where it should be, on the library maintainers' side. Honestly, pain is a bit of an exageration.
You can have a look at filter
/map
to see how an operator is built from the ground up.
You can have a look at unique
to see how an operator is built on top of another one.
Let's say you have
This is roughly equivalent to
"roughly" because we need to take in account the iterators lifetime as well as the space and state they may use.
Because they are all objects, the state is limited to the lifetime of the iterators/streams and cannot be accessed outside unlike tagged
and superSpecial
.
The async API is a set of tools that abstract away all the burden of maintaining and using asynchrony.
scl::async::Mutexed<T>
solves one problem : automatic guard when using data across asynchronous contexts.
Used with scl::async::with
, an abstraction over resource management (e.g. you could adapt it for the filesystem), it becomes one of the most useful tools to use the Monitor pattern :
Think of scl::async::with
as the try-with-resources
of Java or the using
of C# or even the classic RAII of C++, but with functions.
Why the use of functions instead of wrapping in a block? It's nicer to read and reason about :
lock
and unlock
)With the function approach, we're closer to monitors and actually have a distinctive way to describe operations and their limited scope. You often have a function (or a way to make one) when you're dealing with this kind of code, why not make one then?