Experimental tubez program that calculates sum of
first N
numbers
count(uint64_t(0))
| take(N)
| sum<uint64_t>();
Here count
generates stream of numbers uint64_t
from 0 toward
infinity modulo 2^64. It take
-s first N
numbers from infinite
stream and calculates sum
in drain. Operator |
works similar as same
operator from unix pipes.
We should expect tubez expression from example to compile into
something similar as code using for
loop.
uint64_t sum_digits_for(uint64_t N) {
uint64_t r = 0;
for (uint64_t i = 0; i < N; ++i) {
r += i;
}
return r;
}
Both implementations results in exactly same binary code when
comparing either clang
and gcc
output for each of implementations.
- It requires compiler supporting c++14 (tested with
gcc-6
andclang-3.6
). - boost libraries 1.61 or better due to requrement of hana. Additionaly it requires boost::optional.
From project euler 1 problem:
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000.
// use N = 1000 to solve
auto pe1(uint64_t N) {
auto t =
tubez::iter(uint64_t(0), [](auto a) { return a + 1; }) | // source
tubez::filter([](auto n) { return (n % 3 == 0) || (n % 5 == 0); }) | // tube
tubez::take_while([N](auto n) { return n < N; }) | // tube
tubez::sum<uint64_t>(); // drain
return run(t);
}
Composition here works by composing
source | tube | {tube} | drain
Stream composition works in way that we compose one source
than any
number of tubez
and finaly drain
. Under the hood it results in type
called capsule
as closed tube on both ends.
Composition is associative
(a | b) | c == a | (b | c)
this enables making new tubez from other tubez by tubing them in tubez.
Source streams are stream generators. They generate stream of data out of thin air.
template <typename T>
auto count(T from, T step = T(1));
// return source
// 1. argument starting number
// 2. argument step (default value is 1)
auto iter(S state, F &&fun);
/// returns source
/// 1. argument state S
/// 2. argument function F that has type A(*S).
/// Function fun gets called for each input and creates stream of type A
///
/// example - implements count
template <typename T>
static auto count(T from, T step = T(1)) {
return iter(from, [step](T a) { return a + step; });
}
template <typename Iter>
auto from_range(Iter begin, Iter end)
/// returns source
/// 2 arguments that are `begin` and `end` from where data is generated
///
/// example - source stream from vector
std::vector<int> v({1,2,3});
auto src = from_range(v.cbegin(),v.cend());
Tube stream can modify, limit or filter stream. In general it is something, that takes single data from source and sends it down towards drain.
template <typename F>
auto map(F f)
/// returns tube
///
/// argument - function B(A)
/// transforms received elements of A into elements of B
///
/// example: tube that multiply recievd elements by 2
map([](int num)-> std::string {
return num * 2;
})
template <typename T>
auto take(T n)
/// returns tube
///
/// argument - number n
/// closes stream when n numbers pass this tube
///
/// example allow only 5 elements go trough
take(5)
template <typename P>
auto filter(P p)
/// returns *tube*
///
/// argument function *bool(A)* as predicate
/// when predicate evaluates true upstream elements from source are send to downstream
///
/// example: only even numbers from source goes down to drain
filter([](int i)-> bool { return i % 2 == 0;})
template <typename P>
auto take_while(P p)
/// returns *tube*
///
/// argument function *bool(A)* as predicate
/// when predicate evaluates false tubes stops sending data and terminates stream
///
/// example: stops stream when number is too large
take_while([](int i)-> bool { return i <= 3;})
Drain is last element in strem processing chain. It can be seen as data receiver and aggregation.
template <typename R, typename T>
auto reducer(T &&t, R r)
/// returns *drain*
///
/// 1. argument inital state
/// 2. argument function of type void(T& state,A val)
/// 1. argument of this function is reference on state
/// 2. argument is received stream element
///
/// final value of reduction is final state when stream terminates
///
/// example: make drain sum() that calculates total of received integers
static auto sum(){
return tubez::reducer(int(0), [](int& a,const int val){
a += val;
});
}
template <typename R, typename T>
auto reducer_pure(T &&t, R r)
/// returns *drain*
///
/// 1. argument inital state
/// 2. argument function r of type T(T,A)
/// where 1. argument is previous state
/// 2. argument is received stream element
/// example: make drain sum() that calculates total of received integers
static auto sum(){
return tubez::reducer_pure(int(0), [](int a,int val){
return a + val;
});
}
template <typename T>
static auto push_back(T &&t)
/// returns *drain*
///
/// 1. argument container T that has "push_back" method
///
/// make vector with elements from 0 to 9
std::vector<int> v;
auto tube = count(0) | take(10) | push_back(v);
std::vector<int> v0_9 = run(tube);
template <typename T>
static auto sum()
/// returns *drain*
///
/// accumulates received elements in sum
/// example - calculate sum of first 42 natural numbers
count(1) | take(42) | sum<int>();