DEV Community

Angelo
Angelo

Posted on

Partial Lenses in Javascript

Retrieved from partial.lenses
Lenses are basically an abstraction for simultaneously specifying operations to update and query immutable data structures. Lenses are highly composable and can be efficient. A partial lens can view optional data, insert new data, update existing data and remove existing data and can, for example, provide defaults and maintain required data structure parts.

Dealing with large nested data sets in javascript can be cumbersome.

Getting and Setting some deep nested data in a clean efficient and immutable way is possible using Lenses. With a single line of code we can safely get, update or set a deeply nested item, creating a new JSON object with the changes.

The objective of this write up is to show some examples using Lenses VS vanilla JS.
This is the data set that we will be working with.

const user = {
 name: 'Roger',
 phone: {
   cell: '212-334-1212'
 },
 address: {
   street: '123 First Ave',
   zip: '90210'
 },
 car: {
   daily: [
     {
       make: 'Jeep',
       year: 2013
     },
     {
       make: 'Mazda',
       year: 2018
     }
   ],
   weekend: [
     {
       make: 'Corvette',
       year: 1993
     }
   ]
 }
}
Enter fullscreen mode Exit fullscreen mode

Lets see how we can pull out some data from this data set.
This example will show both Vanilla js and Lenses.
Although both of these examples seem pretty clean, the Vanilla example is susceptible to runtime errors. With the Vanilla example, if any element is not found we will get a runtime error.

In this particular example though, both instances will return Jeep.

Get

import * as L from 'partial.lenses'

L.get(['car', 'daily', 0, 'make'], user) // Jeep

user.car.daily[0].make // Jeep

Enter fullscreen mode Exit fullscreen mode

But what happens if we try to access a record or field that does not exist.

L.get(['car', 'daily', 3, 'make'], user) // undefined

user.car.daily[3].make // Cannot read property 'make' of undefined 
Enter fullscreen mode Exit fullscreen mode

Our Vanilla example will trigger a runtime error. In order to achieve the same level of safety we are getting with lenses we need to write something a little more robust.

See below.

// GROSS
if(user && user.car && user.car.daily && user.car.daily.length > 2) {
   return user.car.daily[3].make
} else {
   return undefined
}
Enter fullscreen mode Exit fullscreen mode

Pretty impressive so far?

Compared to the imperative code that checks each field as we drill down, I'd say we get a pretty big net gain with when using Lenses. It's simply cleaner and much easier to reason about.

Defaults

Defaults provide us to set a default value for an element that is missing.

L.get(['phone', 'home', L.defaults('Number not found')], user) // Number not found
Enter fullscreen mode Exit fullscreen mode

If the value is not missing, L.defaults will be ignored. Pretty sweet.

Set

Alright, let's set some data.
Say we want to set a new car in our weekend drivers array.

L.set(['car', 'weekend', L.appendTo], {make: 'Ford', year: 1979}, user)
// Returns the entire User object with the new car record
Enter fullscreen mode Exit fullscreen mode

Notice the L.appendTo. I'd say this speaks for itself. In short this will retain any existing records found in car.weekend and simply append our new record.

Something that you may find interesting.

Say that our object did not have a car and weekend element. Our above Lense would create and set car.weekend as well as adding the new car record to the weekend array. What?!?!

That's right folks. You gotta try it to believe it.

I should point out that we are really just scratching the surface. When I first came across Lenses I had difficulty understanding how so few people, at least in my circles had not even heard of them.

I'd like to add one more example! Lets checkout collect

Collect

Collect safely traverses an array.

L.collect(['car', 'daily', L.elems, 'make'], user) // [ 'Jeep', 'Mazda' ]
Enter fullscreen mode Exit fullscreen mode

In this case we drill down to car.daily.
L.elems represents our traversal over elements.
And finally make is the element we are looking for.

As the name suggests, collect collects results and returns them to us in an array.

If any of the elements in our collect do not exist, we will simply get back an empty array.

How about that ?

:]

You gotta check out Partial Lenses

Top comments (0)