Функциональный микроскоп: линзы в C++

46
Functional Microscope: Lenses in С++ Alexander Granin [email protected] C++ Siberia, Novosibirsk

Transcript of Функциональный микроскоп: линзы в C++

Functional Microscope:Lenses in С++

Alexander [email protected]

C++ Siberia, Novosibirsk

Me?

● C++, Haskell, C#

● C++ User Group Novosibirsk, 2014

● Talks, articles, FP evangelism...

● LambdaNsk - Novosibirsk FP-community

● Kaspersky Lab

struct Presentation{

FP in C++?..Functional Lensescpp_lenses library

};

4

FP in C++?..

С++ User Group Novosibirsk, 2014

FP concepts in C++

● Lambdas, closures, functionals (almost pure)

● Immutability, POD-types

● Templates - pure functional language

● FTL - Functional Template Library

● Initialization lists

● for_each(), recursion

7

Functional Lenses

Lens 2 Lens 3Lens 1

8

Matryoshka

struct Account { Person person; };

struct Person { Address address; };

struct Address{ std::string street; int house; int flat;};

9

Mutable variables...void setStreet(Account& account, const std::string& newStreet){ account.person.address.street = newStreet;}

10

void setStreet(Account& account, const std::string& newStreet){ account.person.address.street = newStreet;}

Mutable variables...

● Easy to break the client code● Demetra law is violated● Highly specific code● Boilerplate

11

Mutable state...void Account::setStreet(const std::string& newStreet) { this->person.address.street = newStreet;}

12

Mutable state...void Account::setStreet(const std::string& newStreet) { this->person.address.street = newStreet;}

● Demetra law is violated● Highly specific code● Mixing of different layers● SRP is violated● Not a POD type

13

Account setStreet(Account account, const std::string& newStreet){ account.person.address.street = newStreet; return account;}

Immutable approach…

14

Account setStreet(Account account, const std::string& newStreet){ account.person.address.street = newStreet; return account;}

Immutable approach… Not so good.

● Easy to break the client code● Demetra law is violated● Highly specific code● Boilerplate

Ok, Lenses!

auto lens = zoom(personLens(), addressLens(), streetLens());

auto newAccount = set(lens, oldAccount, std::string("New street"));

● “Focused” internal element of the structure● Do something with the element from outside● Hiding data structure realization● Fully immutable, composable and reusable

Ok, Lenses!

auto lens = zoom(personLens(), addressLens(), streetLens());

auto newAccount = set(lens, oldAccount, std::string("New street"));

So, how does this work?

Open matryoshka, pull out matryoshka...

Account account = {...};

Person person = getPerson(account); Address address = getAddress(person); std::string street = getStreet(address);

std::string newStreet = "Churchill's " + street;

Address newAddress = setStreet(address, newStreet); Person newPerson = setAddress(person, newAddress); Account newAccount = setPerson(account, newPerson);

Person getPerson(const Account& account) { return account.person;}

Account setPerson(Account account, const Person& person) { account.person = person; return account;}

getA(), setA()

auto getPerson = [](const Account& account) { return account.person;};

auto setPerson = [](Account account, const Person& person) { account.person = person; return account;};

Getter, Setter

auto getPerson = [](const Account& account) { return account.person;};

auto setPerson = [](Account account, const Person& person) { account.person = person; return account;};

Getter, Setter

std::function<Focus(Value)> getter;std::function<Value(Value, Focus)> setter;

template <typename Value, typename Focus>struct Lens { std::function<Focus(Value)> getter; std::function<Value(Value, Focus)> setter;};

Lens<Account, Person> personLens = { getPerson, setPerson };

Lens = Getter + Setter

view

template <typename Value, typename Focus>Focus view(const Lens<Value, Focus>& lens, const Value& value) { return lens.getter(value);}

Lens<Account, Person> personLens = { getPerson, setPerson };

Person person = view(personLens, someAccount);

set

template <typename Value, typename Focus>Value set(const Lens<Value, Focus>& lens, const Value& value, const Focus& newFocus) { return l.setter(value, newFocus);}

Lens<Account, Person> personLens = { getPerson, setPerson };

Person person = view(personLens, someAccount);Account newAccount = set(personLens, account, Person(”Santa”, ”Claus”));

Lens composition is Lens too

Lens<Account, Person> personLens = { getPerson, setPerson };Lens<Person, Address> addressLens = { getAddress, setAddress };Lens<Address, std::string> streetLens = { getStreet, setStreet };

auto lens = zoom(personLens, addressLens, streetLens); // Magic zoom!

Account newAccount = set(lens, someAccount, std::string(”Churchill's”));

Lens composition is Lens too

Lens<Account, Person> personLens = { getPerson, setPerson };Lens<Person, Address> addressLens = { getAddress, setAddress };Lens<Address, std::string> streetLens = { getStreet, setStreet };

auto lens = zoom(personLens, addressLens, streetLens); // Magic zoom!

// getPerson, getAddress, setStreet, setAddress, setPersonAccount newAccount = set(lens, someAccount, std::string(”Churchill's”));

26

cpp_lens library

Manual lensestemplate <typename Value, typename Focus>Lens<Value, Focus> lens(const std::function<Focus(Value)>& getter, const std::function<Value(Value, Focus)>& setter) { Lens<Value, Focus> l; l.getter = getter; l.setter = setter; return l;}

auto personL = lens<Account, Person>( [](const Account& a) { return a.person; }, [](Account a, const Person& p) { a.person = p; return a; });

Autolensesstruct Account { Person person; std::string login; std::string password;};

#define MK_LENS(A, B, member) Lens<A, B> member##L() { \ return lens<A, B> ( GETTER(A, member), SETTER(A, B, member)); }

MK_LENS(Account, Person, person) // personL()MK_LENS(Account, std::string, login) // loginL()MK_LENS(Account, std::string, password) // passwordL()

zoomLens<A, B> lens = aToB;???<A, B, C> lens = zoom(aToB, bToC);???<A, B, C, D> lens = zoom(aToB, bToC, cToD);

zoom (not generic) -> LensStackLens<A, B> lens = aToB;LensStack<A, B, C> lens = zoom(aToB, bToC);LensStack<A, B, C, D> lens = zoom(aToB, bToC, cToD);

template <typename A, typename B, typename C>LensStack<A, B, C> zoom(...) { … }

template <typename A, typename B, typename C, typename D>LensStack<A, B, C, D> zoom(...) { ... }

LensStack (not generic)template <typename A, typename B, typename C = Id, typename D = Id>struct LensStack { Lens<A, B> lens1; Lens<B, C> lens2; Lens<C, D> lens3;};

LensStack<A, B, C> lens = zoom(aToB, bToC); // OKLensStack<A, B, C, D> lens = zoom(aToB, bToC, cToD); // OK

LensStack<A, B, C, D, E> lens = zoom(aToB, bToC, cToD, dToE); // Ooops!

LensStack: Variadic Templates + magic

template<typename L, typename... Tail>struct LensStack<L, Tail...> : LensStack<Tail...>{ typedef LensStack<Tail...> base_type;

LensStack(L lens, Tail... tail) : LensStack<Tail...>(tail...) , m_lens(lens) {}

base_type& m_base = static_cast<base_type&>(*this); L m_lens;};

Infix literal `to` combinator!

auto lens1 = addressL to houseL;auto lens2 = personL to lens1;

auto lens3 = aL to bL to cL to dL to … to theLastOneLens;

auto lens = (a to b) to c; // OK, left-associativeauto lens = a to (b to c); // Error

`to`: proxy + overloading + reroll stackstruct Proxy {...} proxy;

template <typename L1, typename L2>LensStack<Lens<L1, L2>> operator<(const Lens<L1, L2>& lens, const Proxy&)

{ return LensStack<Lens<L1, L2>>(lens); }

template <typename LS, typename L>typename LS::template reroll_type<L> operator>(const LS& stack, const L& lens)

{ return stack.reroll(lens); }

// `Infix literal operator` trick#define to < proxy >

set

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

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

set, over

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

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

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

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

What about containers?struct Car { std::string model; };

std::vector<Car> cars = { Car{"Ford Focus"}, Car{"Toyota Corolla"} };

toListOf *struct Car { std::string model; };

std::vector<Car> cars = { Car{"Ford Focus"}, Car{"Toyota Corolla"} };

std::list<std::string> result = toListOf(folded<Car>() to modelL(), cars);

// result: {"Ford Focus", "Toyota Corolla"}

* toListOf() and folded<T>() is a hack now, sorry...

traversedstruct Account { Person person; };struct Person { std::vector<Car> cars; };struct Car { std::string model; int number; };

auto toCarL = personL() to carsL() to traversed<Car>();

traversed + setstruct Account { Person person; };struct Person { std::vector<Car> cars; };struct Car { std::string model; int number; };

auto toCarL = personL() to carsL() to traversed<Car>();

Account newAccount1 = set(toCarL to modelL(), oldAccount, std::string(“Toyota”));

traversed + overstruct Account { Person person; };struct Person { std::vector<Car> cars; };struct Car { std::string model; int number; };

auto toCarL = personL() to carsL() to traversed<Car>();

Account newAccount1 = set(toCarL to modelL(), oldAccount, std::string(“Toyota”));

std::function<std::string(std::string)> modifier = [](int old) { return old + 6; };Account newAccount2 = over(toCarL to numberL(), newAccount1, modifier);

traversed + traversed!struct Account { Person person; };struct Person { std::vector<Car> cars; };struct Car { std::string model; int number; std::list<std::string> accessories; };

auto toAccessoryL = personL() to carsL() to traversed<Car>() to accessoriesL() to traversed<std::string>();

cpp_lenses library

● Highly experimental

● Done: composing; set, view, over, traverse

● TODO: filter, traverse++, fold, prisms, fusion…

● TODO: clean it, make it wise, short and robust

● github.com/graninas/cpp-lenses

● Complex structures processing

● Test data preparation

● Some XPath, LINQ analogue

● Functional approach

● Much better than just <algorithm>

● ...Why not? Functional C++ is reality coming now

Why lenses in C++?

Thank you!

Alexander [email protected]

Any questions?

C++ Siberia, Novosibirsk

Rerolling LensStacktemplate<typename L1, typename... Tail>struct LS<L1, Tail...> : LS<Tail...>{ template <typename Reroll, typename Lx> void reroll_(Reroll& rerolled, const Lx& lx) const { rerolled.m_lens = m_lens; base.reroll_(rerolled.base, lx); }

template <typename Lx> LensStack<L1, Tail..., Lx> reroll(const Lx& lx) const { LensStack<L1, Tail..., Lx> rerolled; rerolled.m_lens = m_lens; base.reroll_(rerolled.base, lx); return rerolled; }}

// Recursion basetemplate <typename... Tail>struct LensStack{ template <typename Reroll, typename Lx> void reroll_(Reroll& rerolled, const Lx& lx) { rerolled.m_lens = lx; }};