Practical Functional Programming in C++ - Bryce Adelstein-Lelbach - CppCon 2014
Practical Functional Programming in C++ - Bryce Adelstein-Lelbach - CppCon 2014
Programming
Bryce Adelstein-Lelbach
Introduction
• What can we do with FP in C++?
– Write powerful, generic algorithms:
std::sort(RAIterator begin, RAIterator end, Compare comp)
int main() {
A a = f;
B b1 = f;
B b2 = &f; Which
C c1 = f;
C c2 = &f;
lines won’t
b1(); compile?
b2();
c1();
c2();
(*c1)();
(*c2)();
}
The Joys of Function Pointers
int f() { return 42; }
int main() {
A a = f; // A function cannot be defined in this fashion
B b1 = f;
B b2 = &f;
C c1 = f;
C c2 = &f;
b1();
b2();
c1();
c2();
(*c1)();
(*c2)();
}
The Joys of Function Pointers
int f() { return 42; }
int main() {
A a = f; // A function cannot be defined in this fashion
B b1 = f;
B b2 = &f; // No implicit conversion from a pointer to a reference
C c1 = f;
C c2 = &f;
b1();
b2();
c1();
c2();
(*c1)();
(*c2)();
}
The Joys of Function Pointers
int f() { return 42; }
int main() {
A a = f; // A function cannot be defined in this fashion
B b1 = f;
B b2 = &f; // No implicit conversion from a pointer to a reference
b1();
b2();
c1();
c2();
(*c1)();
(*c2)();
}
Lambda Expressions
• C++11 lambda expressions
[] (int A, int B) { return A + B; }
• These expressions have an implementation-defined
type (which is Callable!), but they work with type
deduction:
auto Add = [] (int A, int B) { return A + B; }
• If all return statements in a lambda return the
same type, the return type is implicitly determined.
Otherwise, it needs to be explicitly specified:
[](int A, int B) -> int { return A + B; }
Polymorphic Function Wrapper
• std::function<Signature>
– Signature is a function type:
• R T(Arg0, Arg1, /* ... */ ArgN)
• int(int, int)
– std::function<> has value semantics
– Any Callable that matches the signature can be
stored in std::function<>; std::function<> gives us a
uniform syntax for invoking Callables
std::function<int(int, int)> Add
= [](int A, int B) { return A + B; };
std::cout << Add(4, 5) << std::endl;
V-Tables in MUDs
• MUDs
– Text-based “MMOs”
– Reverse telnet over TCP
– Game world consists of
rooms, items, mobs (e.g.
AI-controlled characters)
and players.
– An in-house scripting
language is normally used
to make the game world
programmable.
• Roomprogs: scripts attached
to rooms.
V-Tables in MUDs
echo The scaly monstrosity shifts
in its slumber as %P enters
the chamber from the eastern
tunnel
wait 10
force dragon wake
// ... do horrible things to player
V-Tables in MUDs
struct room
{
int room_id;
std::string name;
std::string description;
// ...
struct enter_trigs
{
std::function<void(room&, character&)> east;
// ...
};
};
Example: V-Tables in MUDs
bool room::update_enter_trigger(
room_direction dir, std::string script
)
{
std::function<void(room&, character&)> F
= CompileTheScript(script);
if (!F)
return false; // Bad script, fire script dev.
else
{
this->enter_trigs[dir] = F;
this->commit_to_database(); // Update DB.
return true;
}
}
Example: V-Tables in MUDs
void room::enter_trigger(
room_direction dir, character& char
)
{
if (this->enter_trigs[dir])
{
// Room has an enter trigger for this dir
this->enter_trigs[dir](*this, char);
}
// Room has no relevant trigger, do nothing.
}
HIGHER-ORDER FUNCTIONS
Higher-Order Functions
• A function is higher-order if:
– It takes one or more Callables as parameters
OR
– It returns a Callable
• Some examples:
– std::for_each
– std::bind
Crafting Functions
• Higher-order functions allow us to transform
calling conventions:
int f(int a, float b, std::string const& c);
– There are two types of transformations we might
want to make to a function’s argument list:
• Increase/decrease function arity (by binding certain
parameters to a particular value).
• Reorder parameters.
Forwarding Call Wrapper
• std::bind(F, Args&&… args)
– Returns an unspecified type (which is Callable!); this
object is called a binder.
– Each argument can be one of four things:
• If F is a member function, then the first argument must be a
reference or pointer to an instance of the class that F belongs to.
– std::bind(&A::F, this)
• Placeholders: _1, _2, _3, …
– Placeholders denote the arguments passed to the binder object.
• Reference wrappers: std::ref, std::cref
– Bind a parameter by reference/const reference
• Other arguments are passed using value semantics
Binding Parameters
int f(int a, float b, std::string const& c);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Echo Server w/ Asio + Lambdas
int main() {
asio::io_service io_service;
asio_tcp::endpoint endpoint(asio_tcp::v4(), 2000);
asio_tcp::acceptor acceptor(io_service, endpoint);
for (;;) {
asio_tcp::socket socket(io_service); acceptor.accept(socket);
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
Something (Pseudo)Random
std::vector<std::uint32_t>
xorwow(std::size_t N, std::array<std::uint32_t, 6> seed) {
std::vector<std::uint32_t> r(N, 0);
concurrency::array_view<std::uint32_t, 1> rv(N, r);
concurrency::array_view<std::uint32_t, 1> sv(6, seed);
concurrency::parallel_for_each(
rv.extent,
[=](concurrency::index<1> idx) restrict(amp) {
std::uint32_t x = sv[0] << idx[0], y = sv[1], z = sv[2],
w = sv[3], v = sv[4], d = sv[5];
rv[idx] = (d += 362437) + v;
}
);
return r;
}
stellar.cct.lsu.edu
A (PARALLEL) ALGEBRA OF FUNCTIONS
Calling Functions
Synchronous Asynchronous
(returns R) (returns future<R>)
stellar.cct.lsu.edu 60
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}
return principal.get();
}
stellar.cct.lsu.edu 61
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}
return principal.get();
}
stellar.cct.lsu.edu 62
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}
return principal.get();
}
stellar.cct.lsu.edu 63
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}
return principal.get();
}
stellar.cct.lsu.edu 64
Transform-Reduce
std::vector<double> xvalues = // ...
std::vector<double> yvalues = // ...
future<double> result =
parallel::transform_reduce(
parallel::task,
boost::counting_iterator<size_t>(0),
boost::counting_iterator<size_t>(yvalues.size()),
0.0,
std::plus<double>(),
[&xvalues, &yvalues](size_t i) {
return xvalues[i] * yvalues[i];
}
);
Forming Dependencies
• We can also wait for multiple events:
– when_all(a0, a1, ...)
• Depend on futures a0, a1, ... all of which return R.
• Returns future<std::vector<R> >.
– when_all(a, b, ...)
• Depend on futures a, b, ... which return R0, R1, ...
respectively.
• Returns future<std::tuple<R0, R1, ...> >.
– when_all(v)
• Depend on v, an std::vector<future<R> > all of which
return R.
• Returns future<std::vector<R> >.
stellar.cct.lsu.edu 66
1D Heat Equation
typedef future<double> partition;
typedef std::vector<partition> space;
stellar.cct.lsu.edu 67
CONCLUSIONS
Conclusions
• What can we do with FP in C++?
– Write powerful, generic algorithms:
std::sort(RAIterator begin, RAIterator end, Compare comp)