Skip to main content

Using Lenses in PureScript

I believe we all have come across interesting problems while programming and once we found the solution or an easier way to do it, we had our minds blown away. I had a similar feeling when I discovered lenses and started using them. No, I am not talking about my glasses or eye lenses. I am talking about lenses in Haskell or PureScript. Incase you didn't know, we use PureScript at Juspay and after looking at the code for one of our projects, someone came up with a suggestion to use lenses. I had heard of the term, however never found a reason to use it. And now seemed like a good chance to do that.

So let's start with the problem we have and how we would solve it. Let's say we have a lot of records which are wrapped in a constructor. Something like the following:

And now to access the field location in a Person record, we might do something like this:

Or as we have a newtype instance for the Person type we could do this:

What if we want to access location from a Tweet type record?

Feels a bit redundant, doesn't it? Having to type the same thing again and again? There's a solution to this.

Now this is generic enough which will work on all records which have a Newtype instance and have a location field in the record. Also, if you notice the type signature of the function, you will find that we don't specify the type of location which means this would work for any type of the location field.

This is still not intuitive enough, and imagine every time having to write:

But imagine writing something along the lines of

This is how you would do in JavaScript or Python and it feels natural, right?

Here is where lenses come in. At this point you don't need to know how they work underneath. So we will get into how to use them right away.

Basically, you create a lens for the record, the fields, with the getters and setters and then you can use the functions lens provides for getting a field and setting a field. So how to create a lens for the Person type and Tweet type?

The first argument to the lens function is the getter for the location field and second is another function which is setter for the location field.

There's another way to create lenses for a field rather than writing the complete getters and setters. What we do is, we create a lens for the type with the getter and setter and compose the same to get lenses for the fields.

And now you can use something along the lines of: ^. which is an alias for viewOn

Or if we want to use the lens defined using prop it's the same.

However, if you notice, you will find that we are creating a lot of lenses which are redundant. Creating a lens for the same field in different types of records is useless and goes against why we want to use lenses. So let's resuse our solution up in A and make this generic so we can use it in our lens definition.

Now, does this not look generic enough? This would work for any record which has a Newtype instance and also has the field location and as our type definition is generic enough this can be used for any type of field location. And our accessor functions change to:

And to set a value in a record, we would usually do:

Instead, now we can use the functionality lens provides and use the setter function:

So, that's it for a bit of basics on how to get started with lens and hope you understood. And if not, let me know in the comments and I will try to clarify them out.

References:

  1. Lenses by Simon Peyton Jones: Skills Matter
  2. John Weigley: Putting lenses to work: YouTube
  3. PureScript explanation of John Weigley's video by Dominick Gendill: Blog

Comments

Comments powered by Disqus