Adding Async Algorithms to stdC++Russia, Санкт-Петербург
© 2016 Kirk Shoop (github twitter)
1
debounce
5
diagrams courtesy of André Staltz from rxmarbles
1 / 60
1
2 3 4 5 6
6
code (rxcpp)
asyncReadBytes() | tap(printVectorOfBytes) | concat_map(vectorOfStringsFromVectorOfBytes) | group_by(groupFromString) | flat_map(appendGroupStrings) | subscribe(println(cout));
output (emscripten)
65 65 65 65 65 65 13 66 66 66 66 AAAAAA 66 66 66 66 66 66 66 66 66 66 66 66 66 13 67 67 67 67 67 67 67 13 BBBBBBBBBBBBBBBBB CCCCCCC 68 68 68 68 68 68 68 68 68 68 68 68 68 68 13 69 69 69 69 69 69 69 DDDDDDDDDDDDDD 69 69 69 69 69 69 69 69 13 EEEEEEEEEEEEEEE
lines from bytes
© 2016 Kirk Shoop (github twitter) 2 / 60
code (rxcpp)
auto down$ = mousedown$("#window");auto up$ = mouseup$("#window");auto move$ = mousemove$("#window");
down$ | flat_map([=](MouseEvent){ return move$ | take_until(up$) | map([](MouseEvent){return 1;}) | start_with(0) | sum(); }) | map( [](int c){ return to_string(c) + " moves while mouse down"; }) | subscribe(println(cout));
output (emscripten)
2 moves while mouse down
moves while mouse button down
© 2016 Kirk Shoop (github twitter) 3 / 60
code (rxcpp)
struct data { int size; string firstLine;};struct model { map<string, data> store;};
httpGet("http://kirkshoop.github.io/.../README.md") | flat_map([](response_t r) { return r.progress() | combine_latest( [=](progress_t p, vector<uint8_t> d){ return make_tuple(r.url(), p, d); }, r.load()) | scan( model{}, updateModelFromTuple); }) | subscribe(println(cout));
output (emscripten)
README.md, 0 README.md, 0 README.md, 66 README.md, 66 здравствуйте!
bytes from http GET
© 2016 Kirk Shoop (github twitter) 4 / 60
© 2016 Kirk Shoop (github twitter)
1
combineLatest((x, y) => "" + x + y)
2D
5 / 60
A
1A
2
2A
B
2B
C
2C
D
3
3D
4
4D
5
5D
© 2016 Kirk Shoop (github twitter)
scan((x, y) => x + y)
6 / 60
1
1
2
3
3
6
4
10
5
15
why alogrithms?
documented
stable
optimized
descriptive
© 2016 Kirk Shoop (github twitter) 8 / 60
what do algorithms operate on?
© 2016 Kirk Shoop (github twitter) 9 / 60
sequences
© 2016 Kirk Shoop (github twitter) 10 / 60
in space
vector of mouse positions
generator of mouse positions
using mouseMoves = vector<tuple<int,int>>;
sequences
auto mouseMoves(int start, int end) -> std::generator<tuple<int, int>> { for(;start != end; ++start){ auto position = start * 100; co_yield make_tuple(position, position); }}
© 2016 Kirk Shoop (github twitter)
0, 0 100, 100 200, 200 300, 300 400, 400
11 / 60
in time
mouse move events
network packets
auto window::mouseMoves() -> async_generator<tuple<int, int>> { for co_await(auto event : events()) { if (event.id == MOUSEMOVE) { co_yield mousePositionFrom(event); } }}
auto socket::bytes() -> async_generator<vector<byte>> { vector<byte> out; while (out = co_await read(. . .)) { co_yield out; }}
sequences
© 2016 Kirk Shoop (github twitter) 12 / 60
what are the design options?
abstract the sequence or algorithm?
flow control using push or pull?
cancel by ommission or trigger?
chain algorithms using operator| or function nesting?
© 2016 Kirk Shoop (github twitter) 13 / 60
what designs allow async algorithms?abstract flow cancel chain
sequence push trigger operator
© 2016 Kirk Shoop (github twitter) 14 / 60
what might a toy implementation based on these properties look like?
© 2016 Kirk Shoop (github twitter) 16 / 60
push sequence implementations
const auto ints = [](auto first, auto last){ return [=](auto r){ for(auto i=first;i <= last; ++i){ r(i); } };};const auto copy_if = [](auto pred){ return [=](auto dest){ return [=](auto v){ if (pred(v)) dest(v); }; };};const auto printto = [](auto& output){ return [&](auto v) { output << v << endl; };};auto even = [](auto v){return (v % 2) == 0;};
push sequence concepts
struct sender{ template<class Receiver> void operator()(Receiver);};
struct algorithm{ template<class SenderV> SenderV operator()(SenderV);};
struct lifter{ template<class ReceiverV> ReceiverU operator()(ReceiverV);};
struct receiver{ template<class V> void operator()(V);};
© 2016 Kirk Shoop (github twitter) 17 / 60
code
ints(0, 9)(copy_if(even)(printto(cout)));
output (emscripten)
0 2 4 6 8
push sequence
© 2016 Kirk Shoop (github twitter) 18 / 60
code
ints(0, 9) | copy_if(even) | printto(cout);
output (emscripten)
0 2 4 6 8
push sequence
© 2016 Kirk Shoop (github twitter) 19 / 60
what needs to change to support last_or_default?
© 2016 Kirk Shoop (github twitter)
last
20 / 60
1 2 3 4
4
push sequence implementations
const auto last_or_default = [](auto def){ return [=](auto dest){ auto l = make_shared<decay_t<decltype(def)>>(def); return make_receiver( [=](auto v){ l = v; }, [=](){ d(l); d(); }); };};
push sequence concepts
struct receiver{ template<class V> void operator()(V); void operator()();};
© 2016 Kirk Shoop (github twitter) 21 / 60
code
ints(0, 100000000) | copy_if(even) | last_or_default(42) | printto(cout);
output (emscripten)
new ints new copy_if new last_or_default new printto new lifetime last_or_default bound to dest copy_if bound to dest ints bound to dest 99999998 1 values received - done! stopped destructed
what needs to change to support last_or_default?
© 2016 Kirk Shoop (github twitter) 22 / 60
what needs to change to support take?
© 2016 Kirk Shoop (github twitter)
take(2)
23 / 60
1
1
2
2
3 4
push sequence implementations
const auto take = [](int n){ return [=](auto dest){ auto remaining = dest.lifetime. template make_state<int>(n); return make_receiver(dest, remaining, [](auto& d, auto& r, auto v){ if (r-- == 0) { d(); } d(v); }); };};
push sequence concepts
struct subscription{ using stopper = function<void()>; bool is_stopped(); void stop(); void insert(subscription); void erase(subscription); void insert(stopper); template<class Payload, class... ArgN> state<Payload> make_state(ArgN... argn);};
struct receiver{ subscription lifetime; template<class V> void operator()(V); void operator()();};
© 2016 Kirk Shoop (github twitter) 24 / 60
code
async_ints(0, 9) | copy_if(even) | take(3) | printto(cout);
output (emscripten)
new async_ints new copy_if new take new printto new lifetime take bound to dest copy_if bound to dest async_ints bound to dest 0 2 4 3 values received - done! stopped destructed
what needs to change to support take?
© 2016 Kirk Shoop (github twitter) 25 / 60
what needs to change to support failure?
© 2016 Kirk Shoop (github twitter) 26 / 60
what needs to change to support failure?
struct receiver{ subscription lifetime; template<class V> void operator()(V); void operator()(exception_ptr); void operator()();};
© 2016 Kirk Shoop (github twitter) 27 / 60
code
async_ints(0, 9) | copy_if(always_throw) | take(3) | printto(cout);
output (emscripten)
new async_ints new copy_if new take new printto new lifetime take bound to dest copy_if bound to dest async_ints bound to dest always throw! stopped destructed
what needs to change to support failure?
© 2016 Kirk Shoop (github twitter) 28 / 60
what needs to change to support delay?
© 2016 Kirk Shoop (github twitter)
1
delay
30 / 60
2 1
1 2 1
struct schedulable : public worker , public subscription { void operator()();};
struct worker { steady_clock::time_point now();
void schedule(const schedulable& scbl); void schedule( steady_clock::time_point when, const schedulable& scbl);};
struct scheduler { worker create_worker(subscription);};
struct subscription { void unsubscribe(); };
template<class T>struct observer { on_next(T); on_error(exception_ptr); on_completed();};
template<class T>struct subscriber : public observer<T> , public subscription {};
template<class T>struct observable { subscription subscribe(subscriber<T>);};
what needs to change to support delay?
© 2016 Kirk Shoop (github twitter) 31 / 60
code
interval(1s, scheduler) | tap(printproduced) | delay(1500ms, scheduler) | take(5) | subscribe(printemitted);
output (emscripten)
1.0s - 1 produced 2.0s - 2 produced 2.5s - 1 emitted 3.0s - 3 produced 3.5s - 2 emitted 4.0s - 4 produced 4.5s - 3 emitted 4.512s - real time elapsed
what needs to change to support delay?
© 2016 Kirk Shoop (github twitter) 32 / 60
what needs to change to support testing?
© 2016 Kirk Shoop (github twitter) 33 / 60
struct test_worker { steady_clock::time_point now();
void schedule(const schedulable& scbl); void schedule( steady_clock::time_point when, const schedulable& scbl);
void advance_to(long time) const; void advance_by(long time) const; void sleep(long time) const;
template<class T, class F> auto start(F createSource, long created, long subscribed, long unsubscribed) const -> subscriber<T, testable_observer<T>>;};
what needs to change to support testing?
struct recorded { steady_clock::time_point time() const; template<class Observer> virtual accept(const Observer& o) const;};
struct test { using messages_t vector<recorded>;
steady_clock::time_point now() const; test_worker create_worker(subscription cs) const
template<class T> auto make_hot_observable(messages_t m) const -> testable_observable<T>; template<class T> auto make_cold_observable(messages_t m) const; -> testable_observable<T>;};
© 2016 Kirk Shoop (github twitter) 34 / 60
code
auto scheduler = make_test();auto worker = scheduler.create_worker();
auto interval$ = scheduler.make_hot_observable({ on.next(1000, 1), on.next(2000, 2), on.next(3000, 3), on.next(4000, 4)});
auto res = worker.start([&]() { return interval$ | tap(printproduced) | delay(1500ms, scheduler) | take(3) | tap(printemitted);});
output (emscripten)
1.0s - 1 produced 2.0s - 2 produced 2.5s - 1 emitted 3.0s - 3 produced 3.5s - 2 emitted 4.0s - 4 produced 4.5s - 3 emitted 0.026s - real time elapsed emitted value test - SUCCEEDED lifetime test - SUCCEEDED
what needs to change to support testing?
© 2016 Kirk Shoop (github twitter) 35 / 60
what needs to change to support testing?
auto required = rxu::to_vector({ on.next(1000 + 1500, 1), on.next(2000 + 1500, 2), on.next(3000 + 1500, 3)});auto actual = res.get_observer().messages();cout << "emitted value test";if (required == actual) { cout << " - SUCCEEDED" << endl;} else { cout << " - FAILED" << endl; cout << "REQUIRED: " << required << endl; cout << "ACTUAL : " << actual << endl;}
© 2016 Kirk Shoop (github twitter) 36 / 60
what designs do other combinations produce?
© 2016 Kirk Shoop (github twitter) 38 / 60
async_generator
abstract flow cancel chain
sequence pull trigger operator
© 2016 Kirk Shoop (github twitter) 39 / 60
template<class T>struct await_iterator { bool await_ready(); void await_suspend(coroutine_handle<>); async_iterator<T> await_resume();};
template<class T>struct async_iterator { T& operator*(); await_iterator<T> operator++();};
template<class T>struct async_generator { await_iterator<T> begin(); async_iterator<T> end();};
async_generator
abstract flow cancel chain
sequence pull trigger operator
async_generator concepts
© 2016 Kirk Shoop (github twitter) 40 / 60
async_generator
abstract flow cancel chain
sequence pull trigger operator
implement filter operator
template<typename T, typename P> async_generator<T> filter(async_generator<T> s, P pred) { for co_await(auto&& v : s) { if (pred(v)) { co_yield v; } } }
© 2016 Kirk Shoop (github twitter) 41 / 60
async_generator
abstract flow cancel chain
sequence pull trigger operator
use operators to chain algorithms together
auto fiveOddInts = tempSensor() | filter([](int n) { return n % 2 == 0; }) | // discard even take(5); // stop after 5
for co_await(auto n : fiveOddInts) { // . . . }
© 2016 Kirk Shoop (github twitter) 42 / 60
sfrp
abstract flow cancel chain
sequence pull omission function
© 2016 Kirk Shoop (github twitter) 43 / 60
sfrp
abstract flow cancel chain
sequence pull omission function
example
template <typename R, typename Args...>Behavior<R>map(function<R(Args...)> func, Behavior<Args>... behaviors);
Behavior<Drawing> circleFollowsMouse(Behavior<Point2D> mousePos) { return map(circleAt, mousePos);}
© 2016 Kirk Shoop (github twitter) 44 / 60
Спасибо! I enjoyed visiting with you in Санкт-Петербург!
© 2016 Kirk Shoop (github twitter) 45 / 60
complete.
questions?
© 2016 Kirk Shoop (github twitter)
20
merge
20
46 / 60
40
40
60
60
1
1
80
80
100
100
1
1
range-v3
abstract flow cancel chain
sequence pull omission operator
© 2016 Kirk Shoop (github twitter) 48 / 60
range-v3
abstract flow cancel chain
sequence pull omission operator
the range concept
template<class Iterator, class EndIterator = Iterator>struct range { Iterator begin(); EndIterator end();};
© 2016 Kirk Shoop (github twitter) 49 / 60
range-v3 - implement the transform view
// A class that adapts an existing range with a functiontemplate<class Rng, class Fun>class transform_view : public view_adaptor<transform_view<Rng, Fun>, Rng>{ class adaptor : public adaptor_base { // . . . auto get(range_iterator_t<Rng> it) const -> decltype(fun_(*it)) { return fun_(*it); } }; adaptor begin_adaptor() const { return {fun_}; } adaptor end_adaptor() const { return {fun_}; } // . . .};template<class Rng, class Fun>transform_view<Rng, Fun> transform(Rng && rng, Fun fun) { return {std::forward<Rng>(rng), std::move(fun)};}
© 2016 Kirk Shoop (github twitter) 50 / 60
range-v3
abstract flow cancel chain
sequence pull omission operator
use operators to chain algorithms together
auto fiveOddInts = view::ints(0) | //generates next int when asked view::remove_if([](int n) { return n % 2 == 0; }) | // discard even ints view::take(5); // stop after 5
auto result = fiveOddInts | view::to_vector;
© 2016 Kirk Shoop (github twitter) 51 / 60
Transducers
abstract flow cancel chain
algorithm push omission function
© 2016 Kirk Shoop (github twitter) 52 / 60
Transducers
abstract flow cancel chain
algorithm push omission function
the step function
struct transducer { template<class NextStep> struct step { template<class State, class T> auto operator()(State state, T v); template<class State> auto operator()(State state); }; template<class NextStep> auto operator()(NextStep nextstep) { return step<NextStep>(nextstep); }}; © 2016 Kirk Shoop (github twitter) 53 / 60
Transducers
abstract flow cancel chain
algorithm push omission function
implement the filterer transducer
auto filterer = [](auto pred) { return [=](auto step) { return stateless( [=](auto s, auto v) { if (pred(v)) {return step(s, v);} return s; }, [=](auto s){ return step(s); }); }; };
© 2016 Kirk Shoop (github twitter) 54 / 60
Transducers
abstract flow cancel chain
algorithm push omission function
use function nesting to chain algorithms together
auto fiveOddInts = comp( filter([](int n) { return n % 2 == 0; }), // discard even take(5)); // stop after 5
auto result = into(vector<int> {}, fiveOddInts, range(0, 10));
© 2016 Kirk Shoop (github twitter) 55 / 60
rxcpp
abstract flow cancel chain
sequence push trigger operator
© 2016 Kirk Shoop (github twitter) 56 / 60
struct schedulable : public worker , public subscription { void operator()();};
struct worker { steady_clock::time_point now();
void schedule(const schedulable& scbl); void schedule( steady_clock::time_point when, const schedulable& scbl);};
struct scheduler { worker create_worker(subscription);};
struct subscription { void unsubscribe(); };
template<class T>struct observer { on_next(T); on_error(std::exception_ptr); on_completed();};
template<class T>struct subscriber : public observer<T> , public subscription {};
template<class T>struct observable { subscription subscribe(subscriber<T>);};
concepts in rxcpp
© 2016 Kirk Shoop (github twitter) 57 / 60
rxcpp - implement the filter operator
auto filter = [](auto pred) { return [=](auto subscriber) { return make_subscriber( [=](auto v) { if (pred(v)) { subscriber.on_next(v); } }, [=](std::exception_ptr ep) { subscriber.on_error(ep); }, [=]() { subscriber.on_completed(); } ); }};
© 2016 Kirk Shoop (github twitter) 58 / 60
rxcpp
abstract flow cancel chain
sequence push trigger operator
use operators to chain algorithms together
auto fiveOddInts = tempSensor() | filter([](int n) { return n % 2 == 0; }) | // discard even take(5); // stop after 5
fiveOddInts.subscribe([](int n){. . .});
© 2016 Kirk Shoop (github twitter) 59 / 60
Top Related