This post is about one more functional programming idea – LENSES. As you could guess it could be implemented using modern C++.

Concept of lenses from functional programming is actually pretty simple – lens is just pair of getter and setter, which are separated from target object. The main power of lenses is that you could combine them into chain to save a lot of work when you access or modify parts of complex nested structures. The analogy is the following – you can’t get much profit from one lens, but if you combine the bunch of lenses you could make telescope or microscope, etc.

Also there is a big profit when you work with immutable data. In that case a setter is a function which creates a copy of object with modified part. To modify nested structure you could produce very large piece of boilerplate code. But as you will see the lenses could solve this problem brilliantly.

LENS

So a lens is just a pair of functions (getter and setter):

Getter(Object) -> Field + Setter(Object, NewField) -> NewObject

There are several ways to express this using C++ – there is nice presentation here: slides . Another implementation could be found on github – link. And to finish the list there is nice post at Bartosz’s programming cafe – link.

Here is my draft implementation and couple of examples.

// Functional lens: G(T)->F + S(T,F)->T template <typename G, typename S, typename T = std::decay_t< typename function_traits<S>::result >, typename F = std::decay_t< typename function_traits<G>::result > > class Lens { private: G getter; S setter; public: // Constructor - provide getter and setter Lens(G&& g, S&& s) : getter(std::forward<G>(g)), setter(std::forward<S>(s)) {} // getter F operator()(T holder) const { return getter(holder); }; // setter T set(T holder, F&& value) const { return setter(holder, std::forward<F>(value)); } T set(T holder, const F& value) const { return setter(holder, value); } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Functional lens: G(T)->F + S(T,F)->T template < typename G , typename S , typename T = std : : decay_t < typename function_traits < S > : : result > , typename F = std : : decay_t < typename function_traits < G > : : result > > class Lens { private : G getter ; S setter ; public : // Constructor - provide getter and setter Lens ( G && g, S&& s) : getter(std::forward<G>(g)), setter(std::forward<S>(s)) {} // getter F operator()(T holder) const { return getter(holder); } ; // setter T set ( T holder , F && value) const { return setter(holder, std::forward<F>(value)); } T set ( T holder , const F & value) const { return setter(holder, value); } } ;

To construct a lens we could use the following builder:

// Lens builder template <typename G, typename S> auto make_lens(G&& g, S&& s){ return Lens<G,S>(std::forward<G>(g), std::forward<S>(s)); } 1 2 3 4 5 // Lens builder template < typename G , typename S > auto make_lens ( G && g, S&& s){ return Lens<G,S>(std::forward<G>(g), std::forward<S>(s)); }

As example of immutable data let’s grab the example from previous post – about the way to create immutable structures in C++11:

class ScheduleItemData : public IImmutable { public: const int id; const time_t start; const time_t finish; SERIALIZE_JSON(ScheduleItemData, id, start, finish); }; using ScheduleItem = ScheduleItemData::Ptr; class EventData : public IImmutable { public: const int id; const bool isPublic; const string title; const double rating; const vector<ScheduleItem> schedule; const vector<int> tags; SERIALIZE_JSON(EventData, id, isPublic, title, rating, schedule, tags); }; using Event = EventData::Ptr; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class ScheduleItemData : public IImmutable { public : const int id ; const time_t start ; const time_t finish ; SERIALIZE_JSON ( ScheduleItemData , id , start , finish ) ; } ; using ScheduleItem = ScheduleItemData : : Ptr ; class EventData : public IImmutable { public : const int id ; const bool isPublic ; const string title ; const double rating ; const vector < ScheduleItem > schedule ; const vector < int > tags ; SERIALIZE_JSON ( EventData , id , isPublic , title , rating , schedule , tags ) ; } ; using Event = EventData : : Ptr ;

This structure represents some event which has a name, rating, some schedule of time intervals and some integer tags. Let’s start with trivial lens created manually – accessor for title field of event.

auto lensTitle = make_lens([](Event e){ return e->title; }, [](Event e, string title){ return e->set_title(title); }); // Sample event: Event event = EventData(136, true, "Nice event", 4.88, {ScheduleItemData(1,1111,2222), ScheduleItemData(2,3333,4444)}, {45,323,55}); LOG << lensTitle(event) << NL; // Nice event auto correctedEvent = lensTitle.set(event, "Very nice event"); LOG << correctedEvent->title << NL; // Very nice event 1 2 3 4 5 6 7 8 9 10 11 auto lensTitle = make_lens ( [ ] ( Event e ) { return e - > title ; } , [ ] ( Event e , string title ) { return e - > set_title ( title ) ; } ) ; // Sample event: Event event = EventData ( 136 , true , "Nice event" , 4.88 , { ScheduleItemData ( 1 , 1111 , 2222 ) , ScheduleItemData ( 2 , 3333 , 4444 ) } , { 45 , 323 , 55 } ) ; LOG < < lensTitle ( event ) < < NL ; // Nice event auto correctedEvent = lensTitle . set ( event , "Very nice event" ) ; LOG < < correctedEvent - > title < < NL ; // Very nice event

Note: we even created object after the lens.

We could insert constructors for such trivial lenses inside main macro which performs immutability magic! So instead of boilerplate creation of lenses we just need to call static method of immutable class:

auto lensTitle = EventData::lens_title(); 1 auto lensTitle = EventData : : lens_title ( ) ;

So all lens-constructors for basic fields (in current example: isPublic, title, rating, start, finish, etc) are generated automatically.

COMPOSITION

Ok, now main magic – composition of lenses. To make such composition we could make special function (call it zoom for example), or we could use infix operator like the one inside slides. Or we could just use piping again to compose things (post about piping) – so we only need to overload “|” operator.

// Composition of lenses template<typename G1, typename S1, typename T1, typename F1, typename G2, typename S2, typename T2, typename F2> inline auto operator|(Lens<G1,S1,T1,F1> lens1, Lens<G2,S2,T2,F2> lens2) { return make_lens([=](T1 holder)->F2{ return lens2(lens1(holder)); }, [=](T1 holder, F2 value)->T1{ return lens1.set(holder, lens2.set(lens1(holder), value)); }); } 1 2 3 4 5 6 7 8 9 10 11 12 // Composition of lenses template < typename G1 , typename S1 , typename T1 , typename F1 , typename G2 , typename S2 , typename T2 , typename F2 > inline auto operator | ( Lens < G1 , S1 , T1 , F1 > lens1 , Lens < G2 , S2 , T2 , F2 > lens2 ) { return make_lens ( [ = ] ( T1 holder ) - > F2 { return lens2 ( lens1 ( holder ) ) ; } , [ = ] ( T1 holder , F2 value ) - > T1 { return lens1 . set ( holder , lens2 . set ( lens1 ( holder ) , value ) ) ; } ) ; }

Simple!

Let’s imagine we need to modify start time of one item inside our event’s schedule. There is the obvious problem – how to deal with arrays? To solve this we need custom lens which work with lists and focus on specific element of the list. The obvious lens might focus on element inside given vector by index, but to make this more error-proof we could focus on item by id. For example:

// Lens: look at element with specific id in list template <typename C, typename ID, typename T = std::decay_t<decltype(std::declval<C>().front())> > auto lens_list_item_by_id(ID id){ return make_lens( [=](C list){ return list | ffind >> [=](auto el){ return (el->id == id); }; }, [=](C list, T value){ return list | filter >> [=](auto el){ return (el->id != id); } | fappend >> value; }); } 1 2 3 4 5 6 7 8 9 10 11 // Lens: look at element with specific id in list template < typename C , typename ID , typename T = std : : decay_t < decltype ( std : : declval < C > ( ) . front ( ) ) > > auto lens_list_item_by_id ( ID id ) { return make_lens ( [ = ] ( C list ) { return list | ffind > > [ = ] ( auto el ) { return ( el - > id == id ) ; } ; } , [ = ] ( C list , T value ) { return list | filter > > [ = ] ( auto el ) { return ( el - > id ! = id ) ; } | fappend > > value ; } ) ; }

I used functional syntax here, but it could be easily rewritten using any regular syntax as you might guess. So consider it as just as example how it might look.

Now we are ready for final example:

auto lens = EventData::lens_schedule() | lens_list_item_by_id<vector<ScheduleItem>>(1) | ScheduleItemData::lens_start(); auto correctedEvent = lens.set(event, 222); LOG << lens(correctedEvent) << NL; // 222 // Print whole structure LOG << correctedEvent->toJSON() << NL; // {"id":136,"isPublic":true,"title":"Nice event \"3,4\"","rating":4.88,"schedule":[{"id":2,"start":3333,"finish":4444},{"id":1,"start":222,"finish":2222}],"tags":[45,323,55]} 1 2 3 4 5 6 7 8 9 10 auto lens = EventData : : lens_schedule ( ) | lens_list_item_by_id < vector < ScheduleItem > > ( 1 ) | ScheduleItemData : : lens_start ( ) ; auto correctedEvent = lens . set ( event , 222 ) ; LOG < < lens ( correctedEvent ) < < NL ; // 222 // Print whole structure LOG < < correctedEvent - > toJSON ( ) < < NL ; // {"id":136,"isPublic":true,"title":"Nice event \"3,4\"","rating":4.88,"schedule":[{"id":2,"start":3333,"finish":4444},{"id":1,"start":222,"finish":2222}],"tags":[45,323,55]}

We form final lens in one line and then apply it to correct schedule item with id = 1. This could be extended to any kind of complex nested immutable structures. Also you could create any custom lenses as we did to access vector element. It could even have some inner state.

Any way – lenses are brilliant way to modify parts of serious immutable data.

OUT OF FOCUS

There is one minor problem which you probably had experienced when tried to make some photos – sometimes you can’t get object part into focus. The same thing will happen if in previous example you will try to modify schedule item using wrong id. If such id does not exists inside schedule whole chain of lenses will crash. Let’s invent some protection for such case.

Modern camera will not shoot if autofocus system is not locked on something – so let’s add method bool hasFocus(…) to lens. I added this as third must-have parameter to lens constructor but there are probably many other ways.

So lens_item_by_id() and composition operator will be modified accordingly:

// Lens: look at element with specific id in list template <typename C, typename ID, typename T = std::decay_t<decltype(std::declval<C>().front())> > auto lens_list_item_by_id(ID id){ return make_lens( [=](C list){ return list | ffind >> [=](auto el){ return (el->id == id); }; }, [=](C list, T value){ return list | filter >> [=](auto el){ return (el->id != id); } | fappend >> value; }, [=](C list){ return list | fcontains >> [=](auto el){ return (el->id == id); }; } ); } // Composition of lenses template<typename G1, typename S1, typename C1, typename T1, typename F1, typename G2, typename S2,typename C2, typename T2, typename F2> inline auto operator|(Lens<G1,S1,C1,T1,F1> lens1, Lens<G2,S2,C2,T2,F2> lens2) { return make_lens([=](T1 holder)->F2{ return lens2(lens1(holder)); }, [=](T1 holder, F2 value)->T1{ return lens1.set(holder, lens2.set(lens1(holder), value)); }, [=](T1 holder)->bool{ if (!lens1.hasFocus(holder)) return false; return lens2.hasFocus(lens1(holder)); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // Lens: look at element with specific id in list template < typename C , typename ID , typename T = std : : decay_t < decltype ( std : : declval < C > ( ) . front ( ) ) > > auto lens_list_item_by_id ( ID id ) { return make_lens ( [ = ] ( C list ) { return list | ffind > > [ = ] ( auto el ) { return ( el - > id == id ) ; } ; } , [ = ] ( C list , T value ) { return list | filter > > [ = ] ( auto el ) { return ( el - > id ! = id ) ; } | fappend > > value ; } , [ = ] ( C list ) { return list | fcontains > > [ = ] ( auto el ) { return ( el - > id == id ) ; } ; } ) ; } // Composition of lenses template < typename G1 , typename S1 , typename C1 , typename T1 , typename F1 , typename G2 , typename S2 , typename C2 , typename T2 , typename F2 > inline auto operator | ( Lens < G1 , S1 , C1 , T1 , F1 > lens1 , Lens < G2 , S2 , C2 , T2 , F2 > lens2 ) { return make_lens ( [ = ] ( T1 holder ) - > F2 { return lens2 ( lens1 ( holder ) ) ; } , [ = ] ( T1 holder , F2 value ) - > T1 { return lens1 . set ( holder , lens2 . set ( lens1 ( holder ) , value ) ) ; } , [ = ] ( T1 holder ) - > bool { if ( ! lens1 . hasFocus ( holder ) ) return false ; return lens2 . hasFocus ( lens1 ( holder ) ) ; } ) ; }

Now every lenses composition could be checked before run:

auto defocusedLens = EventData::lens_schedule() | lens_list_item_by_id<vector<ScheduleItem>>(10) | ScheduleItemData::lens_start(); LOG << "Is focused?: " << defocusedLens.hasFocus(event) << NL; // FALSE 1 2 3 4 5 auto defocusedLens = EventData : : lens_schedule ( ) | lens_list_item_by_id < vector < ScheduleItem > > ( 10 ) | ScheduleItemData : : lens_start ( ) ; LOG < < "Is focused?: " < < defocusedLens . hasFocus ( event ) < < NL ; // FALSE

Here we tried to focus on element with id 10, which was not in the list. If we provide other object which contains such schedule item – the lens will get focus.

This is nice way to avoid a lot of nullptr checks in real cases.

Focus check could be also implemented through some monadic execution of such chain, but this is another topic.

ADAPTIVE OPTICS

Instead of providing a variable to lens constructor we could use a function as predicate parameter. This functional parameter even could be a mutable function or some external dependence.

For example we could write generic find method as lens:

// Lens: find element in array by predicate template <typename C, typename F, typename T = std::decay_t<decltype(std::declval<C>().front())> > auto lens_find(const F& f){ return make_lens( [=](C list){ return fn_findone(list, f); }, [=](C list, T value){ return list | filter >> [f](auto el){ return !f(el); } | fappend >> value; }, [=](C list){ return list | fcontains >> f; } ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Lens: find element in array by predicate template < typename C , typename F , typename T = std : : decay_t < decltype ( std : : declval < C > ( ) . front ( ) ) > > auto lens_find ( const F & f){ return make_lens( [=](C list){ return fn_findone(list, f); } , [ = ] ( C list , T value ) { return list | filter > > [ f ] ( auto el ) { return ! f ( el ) ; } | fappend > > value ; } , [ = ] ( C list ) { return list | fcontains > > f ; } ) ; }

And use it in the same scheme:

auto sameLens = EventData::lens_schedule() | lens_find<vector<ScheduleItem>>([](auto item){ return (item->id == 1); }) | ScheduleItemData::lens_start(); if (sameLens.hasFocus(event)) LOG << "Value: " << sameLen2(event) << NL; // Value: 1111 1 2 3 4 5 auto sameLens = EventData : : lens_schedule ( ) | lens_find < vector < ScheduleItem > > ( [ ] ( auto item ) { return ( item - > id == 1 ) ; } ) | ScheduleItemData : : lens_start ( ) ; if ( sameLens . hasFocus ( event ) ) LOG < < "Value: " < < sameLen2 ( event ) < < NL ; // Value: 1111

Method hasFocus here could still be used to get rid of nullptr checks and in some cases this could be used as solution to replace MayBe monad (for a price – we need to traverse the chain twice). Also we could extend lens with additional parameters in both get/set methods and constructors and get more complex designs.

CONCLUSION