Функциональный микроскоп: линзы в C++
-
Upload
platonov-sergey -
Category
Software
-
view
1.197 -
download
3
Transcript of Функциональный микроскоп: линзы в C++
Me?
● C++, Haskell, C#
● C++ User Group Novosibirsk, 2014
● Talks, articles, FP evangelism...
● LambdaNsk - Novosibirsk FP-community
● Kaspersky Lab
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
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”));
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++?
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; }};