260 likes | 395 Vues
Dive into the realm of functional programming in C++ with a focus on advanced patterns like monads and futures. This guide details essential concepts including concurrency, first-class functions, memory management, and generic programming, along with practical examples such as sorting algorithms, functional combinators, and asynchronous chaining. Learn how to manage promises and futures effectively while ensuring composability and separation of concerns in library design. Unlock the power of function lifting, exception handling, and value lifting to optimize your C++ applications.
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