Kirk Shoop, Reactive programming in C++

60
Adding Async Algorithms to std C++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

Transcript of Kirk Shoop, Reactive programming in C++

Page 1: Kirk Shoop, Reactive programming in C++

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

Page 2: Kirk Shoop, Reactive programming in C++

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

Page 3: Kirk Shoop, Reactive programming in C++

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

Page 4: Kirk Shoop, Reactive programming in C++

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

Page 5: Kirk Shoop, Reactive programming in C++

© 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

Page 6: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter)

scan((x, y) => x + y)

6 / 60

1

1

2

3

3

6

4

10

5

15

Page 7: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 7 / 60

Page 8: Kirk Shoop, Reactive programming in C++

why alogrithms?

documented

stable

optimized

descriptive

© 2016 Kirk Shoop (github twitter) 8 / 60

Page 9: Kirk Shoop, Reactive programming in C++

what do algorithms operate on?

© 2016 Kirk Shoop (github twitter) 9 / 60

Page 10: Kirk Shoop, Reactive programming in C++

sequences

© 2016 Kirk Shoop (github twitter) 10 / 60

Page 11: Kirk Shoop, Reactive programming in C++

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

Page 12: Kirk Shoop, Reactive programming in C++

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

Page 13: Kirk Shoop, Reactive programming in C++

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

Page 14: Kirk Shoop, Reactive programming in C++

what designs allow async algorithms?abstract flow cancel chain

sequence push trigger operator

© 2016 Kirk Shoop (github twitter) 14 / 60

Page 15: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 15 / 60

Page 16: Kirk Shoop, Reactive programming in C++

what might a toy implementation based on these properties look like?

© 2016 Kirk Shoop (github twitter) 16 / 60

Page 17: Kirk Shoop, Reactive programming in C++

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

Page 18: Kirk Shoop, Reactive programming in C++

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

Page 19: Kirk Shoop, Reactive programming in C++

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

Page 20: Kirk Shoop, Reactive programming in C++

what needs to change to support last_or_default?

© 2016 Kirk Shoop (github twitter)

last

20 / 60

1 2 3 4

4

Page 21: Kirk Shoop, Reactive programming in C++

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

Page 22: Kirk Shoop, Reactive programming in C++

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

Page 23: Kirk Shoop, Reactive programming in C++

what needs to change to support take?

© 2016 Kirk Shoop (github twitter)

take(2)

23 / 60

1

1

2

2

3 4

Page 24: Kirk Shoop, Reactive programming in C++

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

Page 25: Kirk Shoop, Reactive programming in C++

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

Page 26: Kirk Shoop, Reactive programming in C++

what needs to change to support failure?

© 2016 Kirk Shoop (github twitter) 26 / 60

Page 27: Kirk Shoop, Reactive programming in C++

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

Page 28: Kirk Shoop, Reactive programming in C++

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

Page 29: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 29 / 60

Page 30: Kirk Shoop, Reactive programming in C++

what needs to change to support delay?

© 2016 Kirk Shoop (github twitter)

1

delay

30 / 60

2 1

1 2 1

Page 31: Kirk Shoop, Reactive programming in C++

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

Page 32: Kirk Shoop, Reactive programming in C++

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

Page 33: Kirk Shoop, Reactive programming in C++

what needs to change to support testing?

© 2016 Kirk Shoop (github twitter) 33 / 60

Page 34: Kirk Shoop, Reactive programming in C++

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

Page 35: Kirk Shoop, Reactive programming in C++

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

Page 36: Kirk Shoop, Reactive programming in C++

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

Page 37: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 37 / 60

Page 38: Kirk Shoop, Reactive programming in C++

what designs do other combinations produce?

© 2016 Kirk Shoop (github twitter) 38 / 60

Page 39: Kirk Shoop, Reactive programming in C++

async_generator

abstract flow cancel chain

sequence pull trigger operator

© 2016 Kirk Shoop (github twitter) 39 / 60

Page 40: Kirk Shoop, Reactive programming in C++

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

Page 41: Kirk Shoop, Reactive programming in C++

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

Page 42: Kirk Shoop, Reactive programming in C++

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

Page 43: Kirk Shoop, Reactive programming in C++

sfrp

abstract flow cancel chain

sequence pull omission function

© 2016 Kirk Shoop (github twitter) 43 / 60

Page 44: Kirk Shoop, Reactive programming in C++

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

Page 45: Kirk Shoop, Reactive programming in C++

Спасибо! I enjoyed visiting with you in Санкт-Петербург!

© 2016 Kirk Shoop (github twitter) 45 / 60

Page 46: Kirk Shoop, Reactive programming in C++

complete.

questions?

© 2016 Kirk Shoop (github twitter)

20

merge

20

46 / 60

40

40

60

60

1

1

80

80

100

100

1

1

Page 47: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 47 / 60

Page 48: Kirk Shoop, Reactive programming in C++

range-v3

abstract flow cancel chain

sequence pull omission operator

© 2016 Kirk Shoop (github twitter) 48 / 60

Page 49: Kirk Shoop, Reactive programming in C++

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

Page 50: Kirk Shoop, Reactive programming in C++

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

Page 51: Kirk Shoop, Reactive programming in C++

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

Page 52: Kirk Shoop, Reactive programming in C++

Transducers

abstract flow cancel chain

algorithm push omission function

© 2016 Kirk Shoop (github twitter) 52 / 60

Page 53: Kirk Shoop, Reactive programming in C++

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

Page 54: Kirk Shoop, Reactive programming in C++

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

Page 55: Kirk Shoop, Reactive programming in C++

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

Page 56: Kirk Shoop, Reactive programming in C++

rxcpp

abstract flow cancel chain

sequence push trigger operator

© 2016 Kirk Shoop (github twitter) 56 / 60

Page 57: Kirk Shoop, Reactive programming in C++

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

Page 58: Kirk Shoop, Reactive programming in C++

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

Page 59: Kirk Shoop, Reactive programming in C++

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

Page 60: Kirk Shoop, Reactive programming in C++

© 2016 Kirk Shoop (github twitter) 60 / 60