34
Functional “Life”: parallel cellular automata and comonads Alexander Granin [email protected] C++ Russia, Saint Petersburg

Functional "Life": parallel cellular automata and comonads

Embed Size (px)

Citation preview

Page 1: Functional "Life": parallel cellular automata and comonads

Functional “Life”:parallel cellular automata and comonads

Alexander [email protected]

C++ Russia, Saint Petersburg

Page 2: Functional "Life": parallel cellular automata and comonads

Who I am?

● C++, Haskell, C#

● C++ User Group Novosibirsk, 2014“Functional Declarative Design in C++”

● C++ Siberia Novosibirsk, 2015“Functional Microscope: Lenses in C++”

● Talks, articles, research on FP in general, FP in C++

Page 3: Functional "Life": parallel cellular automata and comonads

struct Presentation{

Functional programming in С++Functionally designed cellular automationParallel computation of cellular automation

};

Page 4: Functional "Life": parallel cellular automata and comonads

4

Functional programming in C++

Page 5: Functional "Life": parallel cellular automata and comonads

C++ FP Enthusiasts● Range v3 by Eric Niebler - proposal for C++ Standard Lib

● FTL (Functional Template Library) by Bjorn Aili

● Cat by Nicola Bonelli - Category Theory elements

● Bartosz Milewski

● John Carmack

● …

● <Place your name here>

Page 6: Functional "Life": parallel cellular automata and comonads

С++ User Group Novosibirsk, 2014“Functional Declarative Design in C++”

Page 7: Functional "Life": parallel cellular automata and comonads

С++ Siberia Novosibirsk, 2015“Functional Microscope: Lenses in C++”

auto lens = personL() to addressL() to houseL();

Account account1 = {...};Account account2 = set(lens, account1, 20);// account2.person.address.house == 20

std::function<int(int)> modifier =[](int old) { return old + 6; };

Account account3 = over(lens, account2, modifier);// account3.person.address.house == 26

Lens 2 Lens 3Lens 1

Page 8: Functional "Life": parallel cellular automata and comonads

FP elements in C++

● Lambdas, closures, functions (almost pure)

● std::function<>

● Immutability, POD-types

● Templates - pure functional language

● for_each(), recursion

● C++ Concepts: coming soon...

Page 9: Functional "Life": parallel cellular automata and comonads

9

Simple 1-dimensional 3-state CA

● 1 dimension

● 3 states: A (“Alive”), P (“Pregnant”), D/space (“Dead”),

A AA A

A A

P

P

A A A

1 gen P2 gen A A A3 gen A A A A4 gen A P A5 gen A A A6 gen A A A A7 gen A P A

Page 10: Functional "Life": parallel cellular automata and comonads

template <typename T>struct Universe { std::vector<T> field; int position;};

typedef char Cell;

const Cell Pregnant = 2;const Cell Alive = 1;const Cell Dead = 0;

Universe<Cell>

A A A A

Universe<T>: Pointed array

Universe<Cell> u;u.field = {D, A, A, D, A, A, D};u.position = 3;

Page 11: Functional "Life": parallel cellular automata and comonads

Immutable shift

A A A A

Universe<Cell> left (const Universe<Cell>& u) {

Universe<Cell> newU { u.field, u.position - 1 };

if (u.position == 0) newU.position = u.size() - 1;

return newU;}

Universe<Cell> right (const Universe<Cell>& u);

A A A A

A A A A

shift to right

shift to left

Page 12: Functional "Life": parallel cellular automata and comonads

Observing: shift and extract

A A A A

Cell extract(const Universe<Cell>& u) { return u.field[u.position];}

Universe<Cell> u = {...};

Cell cur = extract (u);

Cell r = extract (right (u));Cell rr = extract (right (right (u)));

Cell l = extract (left (u));Cell ll = extract (left (left (u)));

D

A A A A

shift to left

shift to left

extract

Page 13: Functional "Life": parallel cellular automata and comonads

Rule: observe and reduce

A A A A

P

Cell rule(const Universe<Cell>& row) {

// extract l, ll, cur, r, rr here if (isA(l) && isA(r) && !isAorP(cur)) return Pregnant;

// ... more rules here

return Dead;}

Page 14: Functional "Life": parallel cellular automata and comonads

Applying rule: extend, extractCell rule (Universe<Cell> row);

Universe<Cell> extend ( std::function<Cell(Universe<Cell>)> f, const Universe<Cell>& u);

Cell extract(const Universe<Cell>& u);

A A A A

P

P

Page 15: Functional "Life": parallel cellular automata and comonads

Step: duplicate, (for_each: extend, extract)

A A A A

A A A A

A A A A

A A A A

A A A A

A A A A

A A A A A

P

A

A

P

A

Cell rule (Universe<Cell> row);

Universe<Cell> extend ( std::function<Cell(Universe<Cell>)> f, const Universe<Cell>& u);

Universe<Universe<Cell>>duplicate (const Universe<Cell>& u);

Universe<Cell> left (const Universe<Cell>& u);Universe<Cell> right (const Universe<Cell>& u);

Page 16: Functional "Life": parallel cellular automata and comonads

Universe<Cell> r1;r1.position = 0;r1.field = {D, D, D, P, D, D, D};

Universe<Cell> r2 = stepWith(rule(), r1);Universe<Cell> r3 = stepWith(rule(), r2);

Universe<Cell> ( std::function<Cell(Universe<Cell>)> f, const Universe<Cell>& u) { return extend(f, ut);}

Step

Page 17: Functional "Life": parallel cellular automata and comonads

17

Generic functional approach

#define UT Universe<T>#define UUT Universe<Universe<T>>

template <typename T> T rule (const UT& u)

template <typename T> UT left (const UT& u)template <typename T> UT right (const UT& u)

Page 18: Functional "Life": parallel cellular automata and comonads

Generic extract

template <typename T> T extract(const UT& u){ return u.field[u.position];}

Page 19: Functional "Life": parallel cellular automata and comonads

Generic extend

template <typename T> UT extend ( const func<T(UT)>& f, const UT& u) { UUT duplicated = duplicate (u); return { map(f, duplicated.field), u.position };}

Page 20: Functional "Life": parallel cellular automata and comonads

Generic map

template<typename A, typename B, template <class ...> class Container>Container<B> map ( const std::function<B(A)>& f, const Container<A>& va){ Container<B> vb; std::transform(va.begin(), va.end(), std::back_inserter(vb), f); return vb;}

Page 21: Functional "Life": parallel cellular automata and comonads

Generic duplicate

const std::function<UT(UT)> leftCreator = [](const UT& u) {return left(u); };const std::function<UT(UT)> rightCreator = [](const UT& u) {return right(u); }; template <typename T> UUT duplicate (const UT& u){ return makeUniverse (leftCreator, rightCreator, u);}

Page 22: Functional "Life": parallel cellular automata and comonads

Generic makeUniversetemplate <typename T> UUT makeUniverse ( const std::function<UT(UT)>& leftCreator, const std::function<UT(UT)>& rightCreator, const UT& u) { std::vector<UT> lefts = tailOfGen(u.position, leftCreator, u); std::vector<UT> rights = tailOfGen(u.size() - u.position - 1, rightCreator, u);

std::vector<UT> all; all.swap(lefts); all.push_back(u); all.insert(all.end(), rights.begin(), rights.end());

return { std::move(all), u.position };}

Page 23: Functional "Life": parallel cellular automata and comonads

extract + duplicate + extend = comonad

template <typename T> T extract (const UT& u)

template <typename T> UT extend ( const func<T(UT)>& f, const UT& u)

template <typename T> UUT duplicate (const UT& u)

Page 24: Functional "Life": parallel cellular automata and comonads

24

Parallel computations in FP

Container<B> map ( const std::function<B(A)>& f, const Container<A>& va);

Container<B> mapPar ( const std::function<B(A)>& f, const Container<A>& va);

Page 25: Functional "Life": parallel cellular automata and comonads

mapPar

template <typename A, typename B, template <class ...> class Container>Container<B> mapPar ( const std::function<B(A)>& f, const Container<A>& va){ Container<std::future<B>> pars = map(par(f), va); std::future<Container<B>> pRes = joinPars(pars); return pRes.get();}

Page 26: Functional "Life": parallel cellular automata and comonads

template <typename A, typename B> std::function<std::future<B>(A)> par( const std::function<B(A)>& f){ return [=](const A& a) { return std::async(std::launch::async, [=]() { return f(a); } ); };}

par

Page 27: Functional "Life": parallel cellular automata and comonads

template <typename B> std::future<std::vector<B>> joinPars( std::vector<std::future<B>>& pars){ return std::async(std::launch::async, [&]() { std::vector<B> bs; bs.reserve(pars.size());

for (auto& it : pars) bs.push_back(it.get());

return bs; });}

joinPars

Page 28: Functional "Life": parallel cellular automata and comonads

28

Parallel Game of Life benchmark

● 2 dimensions

● 2 states: A (“Alive”), D/space (“Dead”),

// Pointed array of pointed arraystypedef Universe<Cell> LifeRow;typedef Universe<LifeRow> LifeField;

Page 29: Functional "Life": parallel cellular automata and comonads

A little bit harder...#define UT Universe<T>#define UUT Universe<Universe<T>>#define UUUT Universe<Universe<Universe<T>>>#define UUUUT Universe<Universe<Universe<Universe<T>>>>

template <typename T> UUUUT duplicate2 (const UUT& u)

template <typename T> UUT extend2 ( const func<T(UUT)>& f, const UUT& u)

template <typename T> T extract2 (const UUT& u)

Page 30: Functional "Life": parallel cellular automata and comonads

extend vs extend2template <typename T> UT extend ( const func<T(UT)>& f, const UT& u) { UUT duplicated = duplicate (u); return { map (f, duplicated.field), u.position }; // == fmap (f, duplicated.field)}

template <typename T> UUT extend2 ( const func<T(UUT)>& f, const UUT& uut) { UUUUT duplicated = duplicate2 (uut); return fmap2 (f, duplicated);}

Page 31: Functional "Life": parallel cellular automata and comonads

fmap2

template <typename T> UUT fmap2 ( const func<T(UUT)>& f, const UUUUT& uuut) {

const func<UT(UUUT)> f2 = [=](const UUUT& uuut2) { UT newUt; newUt.position = uuut2.position; newUt.field = map (f, uuut2.field); return newUt; };

return { map (f2, uuut.field), uuut.position }; // parallelization: map -> mapPar}

Page 32: Functional "Life": parallel cellular automata and comonads

Game of Life benchmarkField side Sequential Parallel

(milliseconds)50 484 283

100 3900 2291150 12669 8005200 30278 19415

auto l1 = gliderLifeHuge();

QBENCHMARK { auto l2 = stepWith(rule, l1); QVERIFY(l2.size() == HugeSize);}

Page 33: Functional "Life": parallel cellular automata and comonads

Game of Life on comonads, C++

● Highly experimental

● Sequential, async and parallel GoL

● Simple 1D 3-state CA

● Functional design

● https://github.com/graninas/CMLife

● Клеточные автоматы и комонады, by Hithroc Mehatoko

Page 34: Functional "Life": parallel cellular automata and comonads

Thank you!

Alexander [email protected]

Questions?

C++ Russia, Saint Petersburg