Lenses

Ramda's lenses are pretty great. They allow you to easily return modified versions of objects.

Let's start with some simple non-lens ways to get and set, R.prop and R.assoc:

////purchase
var purchase = {  
    name: 'Hover Board',
    price: 10000
}

////get prop
var get_name = R.prop('name')

get_name(purchase)  
//returns 'Hover Board'

////change prop
var change_price = R.assoc('price')

change_price(100000, purchase)  
//returns { name: 'Hover Board', price: 100000 }

////add prop
var add_radness = R.assoc('radness')  
var add_max_radness = add_radness(11)

add_max_radness(purchase)  
/* returns {
    name: 'Hover Board',
    price: 10000,
    radness: 11 } */

R.prop can be used to retrieve the value of an object's property. R.assoc can be used to modify the value of an object's existing property or add a new one.

We can do the same thing with a lens:

////lens
var price_lens = R.lens(R.prop('price'), R.assoc('price'))

R.view(price_lens, purchase)  
//returns 10000

R.set(price_lens, 20000, purchase)  
//returns { name: 'Hover Board', price: 20000 }

R.lens returns a lens. It takes a getter and a setter. If the getter and setter focus on the same property then R.view acts like R.prop and R.set acts like R.assoc.

If that's all there was to lenses, I wouldn't recommend them. To much work to do what other, easier, functions already do. But then there's R.over:

var apply_discount = R.curry((discount, purchase) =>  
    R.over(
        price_lens,
        R.flip(R.subtract)(discount),
        purchase))

apply_discount(100, purchase)  
//returns { name: 'Hover Board', price: 9900 }

(FYI, R.flip flips the order of a functions arguments :) )

R.over takes a lens, a function, and an object. It applies the function to the value of the property the lens is focusing on and replaces it with the result of that function.

I think this is pretty rad.

R.lensProp is a nice helper for if you want a lens who's getter and setter focus on the same property:

R.view(R.lensProp('price'), purchase),  
//returns 10000

R.set(R.lensProp('price'), 20000, purchase)  
//returns { name: 'Hover Board', price: 20000 }

R.lensPath is also pretty neat. It allows you to focus on a nested object's property:

var ship = {  
    name: 'Enterprise',
    captain: {
        name: 'Picard'
    },
    crew: [
        'Data',
        'Tasha Yar'
    ]
}

R.set(  
    R.lensPath(['captain', 'name']),
    'Riker',
    ship)
/* returns { name: 'Enterprise',
  captain: { name: 'Riker' },
  crew: [ 'Data', 'Tasha Yar' ] } */

And R.lensIndex does for arrays with normal lenses do for objects:

R.over(  
    R.lensProp('crew'),
    R.set(
        R.lensIndex(1),
        'Worf'),
    ship)
/* returns { name: 'Enterprise',
  captain: { name: 'Picard' },
  crew: [ 'Data', 'Worf' ] } */

But I think one of the coolest features of a lens is that the getter and setter don't have to be for the same property. This allows you to add and modify an object's properties based on other properties of the object:

var format_price =  
    R.over(
        R.lens(
            R.prop('price'),
            R.assoc('formatted_price')),
        format_dollars_cents)

format_price(purchase)  
/* returns { name: 'Hover Board',
  price: 10000,
  formatted_price: '$10,000.00' } */

For me, lenses open up a whole new world of function composition. I can now compose a function that cleanly and easily modifies pretty much any data structure I want. I can nest arrays in objects and vice versa as much as I want and still compose a function that returns any transformed version of it I need.

Looking for a software developer?