Skip to content

jonsterling/Lens.hpp

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 

I'm going to assume that you know what Lenses are. In the meanwhile, check out these slides for some basic theory on Lenses. The point of this readme is to explain how I implemented Lenses in C++.

How to generalize member-access

The first problem to solve was generalizing member-access in a statically composable manner. C++ allows you to declare a pointer to a member of a class, which becomes extremely useful. If the following code declares a pointer dog_toy to the a Dog's toy:

Toy Dog::*dog_toy = &Dog::toy;

Then the type of dog_toy is Toy Dog::*. The other key bit of information is that a non-type template parameter can be a pointer to a member. Hence, the following will generalize a structure over a member pointer member_ptr:

template <class A, class B, A B::*member_ptr>

Using Lenses

The most basic, raw way to use lenses in your classes is to provide a typedef to the correct instantiation of the lens template. Later, you can make new lenses by composing existing ones. This could look like:

class Toy
{
  std::string _name;
  typedef lens<Toy,std::string,&Toy::_name> name;

public:
  Toy(std::string n) : _name(n) {}

  const std::string& get_name() const
  {
    return Toy::name(*this).get();
  }

  const Toy set_name(const std::string& n) const
  {
    return Toy::name(*this).set(n);
  }
};


class Dog
{
  std::string _name;
  Toy _toy;
  typedef lens<Dog,std::string,&Dog::_name> name;
  typedef lens<Dog,Toy,&Dog::_toy> toy;

public:
  Dog(std::string n, Toy t) : _name(n), _toy(t) {}
  
  const std::string& get_name() const
  {
    return Dog::name(*this).get();
  }

  const Dog set_name(const std::string& n) const
  {
    return Dog::name(*this).set(n);
  }

  const Toy& get_toy() const
  {
    return Dog::toy(*this).get();
  }

  const Dog set_toy(const Toy& t) const
  {
    return Dog::toy(*this).set(t);
  }
};

This is rather too irritating to do frequently, though. With the help of the C preprocessor, however, we can automate this nicely:

class Toy
{
  std::string val(name);

public:
  Toy(std::string n) : _name(n) {}
};

class Dog
{
  std::string val(name);
  Toy val(toy);

public:
  Dog(std::string n, Toy t) : _name(n), _toy(t) {}
}

The hiccup here, though, is that we need to be able to determine the type of the current class in a general manner. Preferably, C++ would have a type keyword This or Self, which would point to the current class, but that's something we need to implement for ourselves. The way to do this is define a small utility class, Derivable<T>:

template <class T> class Derivable
{
  typedef T Self;
};

Thus, we don't need to pass Toy into the val macro, since it assumes that the class inherits the Derivable trait.

###Composition

Lenses are composed by using the lens_comp veriadic template. For instance:

typedef lens_comp<Person::dog,Dog::toy,Toy::name> person_dog_toy_name;

const Person jon("jon", Dog("tucker", Toy("squeaky")));
const std::string str = person_dog_toy_name(jon).get(); // => "squeaky"

const Person jon2 = person_dog_toy_name(jon).set("fuzzy");
// Now, jon2 is a copy of jon, but with his the name of his Dog's Toy changed.

This is super excellent for dealing with deep structures.

##Deriving Show

In Haskell, we can allow any datatype to be printed by deriving the Show type class automatically. This looks like:

data Toy = Toy String deriving Show
data Dog = Dog String Toy deriving Show

This seemed like an important thing to have in C++, so I've added a similar (static) facility for doing this, using a variadic template over lenses.

template< class T // The class of the object being "shown"
        , class ...Lenses // The object's lenses, in order of desired appearance
        >
struct Show;
~~~

This can be used in a class as follows:

~~~~cpp
class Dog
{
  std::string val(name);
  Toy val(toy);
  
  typedef Show<Dog,name,toy> show_t;

public:
  Dog(std::string n, Toy t) : _name(n), _toy(t) {}

  std::string show() const
  {
    return show_t(*this).show();
  }
};

const Toy t("squeaky");
const Dog d("Tucker", t);
std::cout << d.show() << std::endl; // => "Dog (Tucker, squeaky)"

Of course, a macro derive_show has also been provided, which simpifies derivation by a lot:

class Dog
{
  std::string val(name);
  Toy val(toy);

public:
  Dog(std::string n, Toy t) : _name(n), _toy(t) {}

  derive_show(name,toy);
};

About

Functional Lenses in C++

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages