Functional Microscope:
Lenses in С++
Alexander Granin
graninas@gmail.com
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 Lenses
cpp_lenses library
};
4
FP in C++?..
С++ User Group Novosibirsk, 2014
FP concepts in C++
● Lambdas, closures, functions (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, setPerson
Account newAccount = set(lens, someAccount, std::string(”Churchill's”));
26
cpp_lens library
Manual lenses
template <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; });
Autolenses
struct 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()
zoom
Lens<A, B> lens = aToB;
???<A, B, C> lens = zoom(aToB, bToC);
???<A, B, C, D> lens = zoom(aToB, bToC, cToD);
zoom (not generic) -> LensStack
Lens<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); // OK
LensStack<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-associative
auto lens = a to (b to c); // Error
`to`: proxy + overloading + reroll stack
struct 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...
traversed
struct 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 + set
struct 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 + over
struct 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 Granin
graninas@gmail.com
Any questions?
C++ Siberia, Novosibirsk
Rerolling LensStack
template<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 base
template <typename... Tail>
struct LensStack
{
template <typename Reroll, typename Lx>
void reroll_(Reroll& rerolled, const Lx& lx)
{
rerolled.m_lens = lx;
}
};

Functional microscope - Lenses in C++

  • 1.
    Functional Microscope: Lenses inС++ Alexander Granin graninas@gmail.com C++ Siberia, Novosibirsk
  • 2.
    Me? ● C++, Haskell,C# ● C++ User Group Novosibirsk, 2014 ● Talks, articles, FP evangelism... ● LambdaNsk - Novosibirsk FP-community ● Kaspersky Lab
  • 3.
    struct Presentation { FP inC++?.. Functional Lenses cpp_lenses library };
  • 4.
  • 5.
    С++ User GroupNovosibirsk, 2014
  • 6.
    FP concepts inC++ ● Lambdas, closures, functions (almost pure) ● Immutability, POD-types ● Templates - pure functional language ● FTL - Functional Template Library ● Initialization lists ● for_each(), recursion
  • 7.
  • 8.
    8 Matryoshka struct Account {Person person; }; struct Person { Address address; }; struct Address { std::string street; int house; int flat; };
  • 9.
    9 Mutable variables... void setStreet(Account&account, const std::string& newStreet) { account.person.address.street = newStreet; }
  • 10.
    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.
    11 Mutable state... void Account::setStreet(conststd::string& newStreet) { this->person.address.street = newStreet; }
  • 12.
    12 Mutable state... void Account::setStreet(conststd::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.
    13 Account setStreet(Account account,const std::string& newStreet) { account.person.address.street = newStreet; return account; } Immutable approach…
  • 14.
    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
  • 15.
    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
  • 16.
    Ok, Lenses! auto lens= zoom(personLens, addressLens, streetLens); auto newAccount = set(lens, oldAccount, std::string("New street")); So, how does this work?
  • 17.
    Open matryoshka, pullout 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);
  • 18.
    Person getPerson(const Account&account) { return account.person; } Account setPerson(Account account, const Person& person) { account.person = person; return account; } getA(), setA()
  • 19.
    auto getPerson =[](const Account& account) { return account.person; }; auto setPerson = [](Account account, const Person& person) { account.person = person; return account; }; Getter, Setter
  • 20.
    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;
  • 21.
    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
  • 22.
    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);
  • 23.
    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”));
  • 24.
    Lens composition isLens 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”));
  • 25.
    Lens composition isLens 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, setPerson Account newAccount = set(lens, someAccount, std::string(”Churchill's”));
  • 26.
  • 27.
    Manual lenses template <typenameValue, 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; });
  • 28.
    Autolenses struct Account { Personperson; 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()
  • 29.
    zoom Lens<A, B> lens= aToB; ???<A, B, C> lens = zoom(aToB, bToC); ???<A, B, C, D> lens = zoom(aToB, bToC, cToD);
  • 30.
    zoom (not generic)-> LensStack Lens<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(...) { ... }
  • 31.
    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); // OK LensStack<A, B, C, D> lens = zoom(aToB, bToC, cToD); // OK LensStack<A, B, C, D, E> lens = zoom(aToB, bToC, cToD, dToE); // Ooops!
  • 32.
    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; };
  • 33.
    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-associative auto lens = a to (b to c); // Error
  • 34.
    `to`: proxy +overloading + reroll stack struct 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 >
  • 35.
    set auto lens =personL() to addressL() to houseL(); Account account1 = {...}; Account account2 = set(lens, account1, 20); // house == 20
  • 36.
    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
  • 37.
    What about containers? structCar { std::string model; }; std::vector<Car> cars = { Car{"Ford Focus"}, Car{"Toyota Corolla"} };
  • 38.
    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...
  • 39.
    traversed struct Account {Person person; }; struct Person { std::vector<Car> cars; }; struct Car { std::string model; int number; }; auto toCarL = personL() to carsL() to traversed<Car>();
  • 40.
    traversed + set structAccount { 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”));
  • 41.
    traversed + over structAccount { 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);
  • 42.
    traversed + traversed! structAccount { 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>();
  • 43.
    cpp_lenses library ● Highlyexperimental ● 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
  • 44.
    ● Complex structuresprocessing ● 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++?
  • 45.
    Thank you! Alexander Granin graninas@gmail.com Anyquestions? C++ Siberia, Novosibirsk
  • 46.
    Rerolling LensStack template<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 base template <typename... Tail> struct LensStack { template <typename Reroll, typename Lx> void reroll_(Reroll& rerolled, const Lx& lx) { rerolled.m_lens = lx; } };