0% found this document useful (0 votes)
6 views

Practical Functional Programming in C++ - Bryce Adelstein-Lelbach - CppCon 2014

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Practical Functional Programming in C++ - Bryce Adelstein-Lelbach - CppCon 2014

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 69

Practical Functional

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)

– Write reactions to events:


• GUIs
• IO/networking
• Tasking/parallelism
– Compose new calling conventions from old ones
– Manipulate execution flow as a data structure.
Overview
• First-Class Functions
• Higher-Order Functions
• A (Parallel) Algebra of Functions
FIRST-CLASS FUNCTIONS
First-Class Functions
• The “rights and privileges” of first-class
citizens:
– They may be named by variables.
– They may be passed as arguments to procedures.
– They may be returned as the results of
procedures.
– They may be included in data structures.
(according to SICP/Christopher
Strachey)
The Callable Concept
• C++ functions are not first-class, but we have various
ways of representing functions as first-class entities in
C++.
• The Callable concept generalizes these different
representations.
– A type T is Callable if, given a suitable argument list, we can
invoke T to produce a return value.
• Things that are Callable:
– Pointers to functions
– Pointers to member functions
– Types with call operators
The Joys of Function Pointers
int f() { return 42; }

typedef int A(); // Function type


typedef int(&B)(); // Reference to function type
typedef int(*C)(); // Pointer to function type

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; }

typedef int A(); // Function type


typedef int(&B)(); // Reference to function type
typedef int(*C)(); // Pointer to function type

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; }

typedef int A(); // Function type


typedef int(&B)(); // Reference to function type
typedef int(*C)(); // Pointer to function type

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; }

typedef int A(); // Function type


typedef int(&B)(); // Reference to function type
typedef int(*C)(); // Pointer to function type

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; // Implicit function-reference-to-pointer conversion


C c2 = &f;

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);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Binding Parameters
int f(int a, float b, std::string const& c);

auto g1 = std::bind(&f, 17, _1, _2);


g1(4.5, “hello”); // f(17, 4.5, “hello”);

std::string input = // …
auto g2 = std::bind(&f, _1, _2, std::cref(input));
g2(9, 0.25); // f(9, 0.25, input);

auto g3 = std::bind(&f, _3, _1, _2);


g3(“foobar”, 1024, -85.0); // f(1024, -85.0, “foobar”)
Lambda Closures
• Just like std::bind(), C++11 lambdas can
bind variables from their parent scope.
• The [] at the beginning of the lambda is the
lambda’s closure (aka capture list)
– [] -> no capture
– [&] -> all captures done by ref
– [=] -> all captures done by value
– [&x, =y] -> x by ref, y by value
– [=, &x] -> x by ref, everything else by value
Dangling References
• Capturing a variable in a lambda by reference will not
keep it alive!
• If the lambda is invoked outside of its parent scope, the
behavior is undefined.
• Likewise, std::ref/std::cref are not valid once the
parents scope has exited.
template <typename F>
std::function<int(int)> badcode(F&& f)
{
int A = 42;
return [&] (int B) { return A + B; };
}
Binding Member Functions
• Suppose we want to use std::for_each with
a member function…
MyClass obj;
std::for_each(begin(my_list), end(my_list),
std::bind(&MyClass::add, std::ref(obj), _1)
);

• Or, with lambda expressions:


MyClass obj;
std::for_each(begin(my_list), end(my_list),
[&obj] (int x) { obj.add(x); }
);
Binding Member Functions
• Suppose we want to use std::for_each with
a member function…
MyClass obj;
std::for_each(begin(my_list), end(my_list),
std::bind(&MyClass::add, std::ref(obj), _1)
);

• Or, with lambda expressions:


MyClass obj;
std::for_each(begin(my_list), end(my_list),
[&obj] (int x) { obj.add(x); }
);
Binding Member Functions
• Suppose we want to use std::for_each with
a member function…
void MyClass::add_for_each(std::list<T>& my_list) {
std::for_each(begin(my_list), end(my_list),
std::bind(&MyClass::add, this, _1)
);
}

• Or, with lambda expressions:


void MyClass::add_for_each(std::list<T>& my_list) {
std::for_each(begin(my_list), end(my_list),
[this] (int x) { this->add(x); }
);
}
Echo Server w/ Asio + Lambdas
• Boost.Asio: library for
synchronous and
asynchronous I/O.
– Proactor-based design.
• Provides a generic
framework for various types
of I/O:
– Network sockets.
• Asio provides TCP, UDP and
ICMP support.
– Files.
– Serial ports.
– Interprocess communication.
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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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);

std::size_t const max_length = 1024;


char msg[max_length];

std::function<void(error_code const&, std::size_t)>


f = [&](error_code const& ec, std::size_t bytes)
{
asio::async_write(socket, asio::buffer(msg, bytes),
[&](error_code const& ec, std::size_t)
{
auto buf = asio::buffer(msg, max_length);
socket.async_read_some(buf, f);
});
};

socket.async_read_some(asio::buffer(msg, max_length), f);


io_service.run();
}
}

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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];

std::uint32_t t = (x ^ (x >> 2));


x = y; y = z; z = w; w = v;
v = (v ^ (v << 4)) ^ (t ^ (t << 1));

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>)

f(a...) async(f, a...)


The Future
• async() and future<>s
– future<T>: an object representing a result which
has not been calculated yet.
– async(F, Args&&… args)
• Look familiar? It’s another higher-order function
• async() starts a computation (F) asynchronously; the
return value of that computation is represented by
future<T>
A Parallel Algebra
• The basic operations of futures
– f = async(g, ...) returns future<R>
• Call the function g asynchronously.
– f.then(g) returns future<R>
• Attach a completion handler to the future f.
– make_ready_future(r) returns future<R>
• Create a future with value r that is in the ready state.

stellar.cct.lsu.edu 60
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}

double interest(double init_principal, double init_rate, std::size_t time)


{
future<double> principal = make_ready_future(init_principal);

for (std::size_t i = 0; i < time; ++i)


{
principal = principal.then(std::bind(&add, _1, rate));
}

return principal.get();
}

stellar.cct.lsu.edu 61
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}

double interest(double init_principal, double init_rate, std::size_t time)


{
future<double> principal = make_ready_future(init_principal);

for (std::size_t i = 0; i < time; ++i)


{
principal = principal.then(std::bind(&add, _1, rate));
}

return principal.get();
}

stellar.cct.lsu.edu 62
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}

double interest(double init_principal, double init_rate, std::size_t time)


{
future<double> principal = make_ready_future(init_principal);

for (std::size_t i = 0; i < time; ++i)


{
principal = principal.then([=rate] (double p) { return add(p, rate); });
}

return principal.get();
}

stellar.cct.lsu.edu 63
Interest
double add(double principal, double rate)
{
return principal + principal * rate;
}

double interest(double init_principal, double init_rate, std::size_t time)


{
future<double> principal = make_ready_future(init_principal);

for (std::size_t i = 0; i < time; ++i)


{
principal = principal.then([=rate] (double p) { return add(p, 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;

std::vector<space> U(2, space(nx));


for (std::size_t t = 0; t != nt; ++t)
{
space const& current = U[t % 2];
space& next = U[(t + 1) % 2];

for (std::size_t i = 0; i != nx; ++i)


{
next[i] = when_all(
current[idx(i-1, nx)], current[i], current[idx(i+1, nx)]
).then(
&My1DHeatKernel
);
}
}

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)

– Write reactions to events:


• GUIs
• IO/networking
• Tasking/parallelism
– Compose new calling conventions from old ones
– Manipulate execution flow as a data structure.

You might also like