260 likes | 387 Views
I see a monad in your future. Bartosz Milewski. Functional Patterns in C++. Concurrency First class functions Generic programming Memory Management (move semantics) Math nomenclature Functor Applicative Functor Monad Monoid. Higher Order Functions. Sorting: compare function
E N D
I see a monad in your future Bartosz Milewski
Functional Patterns in C++ • Concurrency • First class functions • Generic programming • Memory Management (move semantics) • Math nomenclature • Functor • Applicative Functor • Monad • Monoid
Higher Order Functions • Sorting: compare function • Find, copy_if: predicate function • Accumulate: binary function for_each(v.begin(), v.end(), [](char c) { cout << c; }); transform(v.begin(), v.end(), w.begin(), [](int x) { return x * x; });
Combinators • Currying, partial application: bind • Combining algorithms v.erase(remove_if(v.begin(), v.end(), bind(logical_and<bool>(), bind(greater<int>(), _1, -10), bind(less<int>(), _1, 10))), v.end());
Future • Channel for passing data (John Reppy, ML) • Promise • Future
Promise promise<string> prms; Thread A Thread B Promise Shared state Page 7
Future promise<string> prms; auto ftr = prms.get_future(); Thread A Thread B Future Promise Shared state Page 8
Create thread promise<string> prms; auto ftr = prms.get_future(); thread th(&thFun, std::move(prms)); Thread A Thread B Future Promise Shared state Page 9
set_value promise<string> prms; auto ftr = prms.get_future(); thread th(&thFun, std::move(prms)); Thread A Thread B Future Promise Thread B Shared state prms.set_value(“Hello from future”); Hello Page 10
get promise<string> prms; auto ftr = prms.get_future(); thread th(&thFun, std::move(prms)); std::string str = ftr.get(); Thread A Thread B Future Promise Thread B Shared state prms.set_value(“Hello from future”); Hello Page 11
Library Design • Composability • Orthogonality (Separation of concerns)
Then Pattern • Problem: Apply a function to a future • future<string> ftr = async(…); • … • string s = ftr.get(); // blocks? • … then continue to parse(s)
Then Combinator • future<string> ftr = async(…); • string s = ftr.get(); // blocks? • … then parse(s) template<typename F> auto future::then(F&& func) -> future<decltype(func(*this))>; future<Tree> fTree = ftr.then([](future<string> fstr) { return parse(fstr.get()); // doesn’t block }); Tree tree = fTree.get(); // blocks? • Next combinator future<Tree> fTree = ftr.next(parse); Tree tree = fTree.get(); // blocks?
Function Lifting • next “lifts” parse to act on futures future<string> fStr = … future<Tree> fTree = fStr.next(parse); vector<int> v = {1, 2, 3}; vector<int> w; w.resize(v.size()); transform(v.begin(), v.end(), w.begin(), square); • transform “lifts” square to act on containers
Type Constructor • Unconstrained parametric polymorphism (universally quantified types) • For all types T: • template<class T> class future; • template<class T> class vector; • template<class T> class unique_ptr; • A mapping of types: • T -> future<T>
The Functor Pattern • Type constructor • Function lifting: then, transform, (Haskell: fmap) • T -> future<T> • fuction<S(T)> -> function<future<S>(future<T>));
Asynchronous Chaining • Problem: Composing (chaining) async calls • future<HANDLE> async_open(string &); • future<Buffer> async_read(HANDLE fh); • In principle, this is the result: • future<future<Buffer>> ffBuf = async_open("foo").next(&async_read);
Unwrap • Collapse two levels of future • async_open("foo.cpp").next(&async_read).unwrap().next(&async_process).unwrap(); • Combination of next and unwrap called bind • (Haskell: >>=, bind combines join with fmap) • In C++, next (then) can be overloaded to serve as bind
Lifting a value • Usage: conditional asynchrony, recursion • A future that is ready • make_ready_future • future<int> fint = make_ready_future<int>(42); • inti = fint.get(); // doesn’t block • Analogously, for containers: • vector<int> v = {42};
Monad Pattern • Functor pattern • Type constructor • Function lifting (then, next, transform) • Collapsing (unwrap, concat) • Value lifting (make_ready_future)
Monad Pattern 2 • Type constructor • Value lifting: make_ready_future() • bind: combination of .next(f).unwrap() [or an overload of next] • Usage of the future monad pattern: • Composing libraries of async functions
Exceptions • It’s all in the wrist • next (or bind) checks for exceptions and propagates them (without calling the continuation) • At the end of the chain, recover from exception • async_open("foo.cpp").next(&async_read).next(parse).recover(&on_error); • Exception monad • Implements short-circuiting • Can be put on top of the future monad (monad transformers)
Applicative Pattern • Problem: Need N futures to proceed. • Create a barrier, get all values, proceed. • when_all: takes futures, returns future of finished futures • Client gets, iterates, gets each, and proceeds with values • Functional approach • Apply a regular function of n argument to n futures. • Lifting of n-ary functions • when_all_done(futures).next(fn) • Together with make_ready_future: applicative functor
Monoid Pattern • Problem: Wait for the first future to complete • when_any: takes futures, returns a future of futures, at least one of them ready • Client gets, iterates, checks is_ready, picks value. proceeds • Functional approach • The OR combinator (like addition?) • Combines two futures into one • Assoc.: (f1 OR f2) OR f3 = f1 OR (f2 OR f3) • Neutral element: the “never” future • never OR f = f = f OR never • Defines a monoid
Conclusions • New patterns based on functional programming • Functor • Applicative Functor • Monad • Monoid • Composabilityand orthogonality • Result: Library of futures