Skip to content

Instantly share code, notes, and snippets.

@jmilamwalters
Last active December 28, 2018 21:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmilamwalters/3e07943f09ad3ae4ceca7abb296f09d1 to your computer and use it in GitHub Desktop.
Save jmilamwalters/3e07943f09ad3ae4ceca7abb296f09d1 to your computer and use it in GitHub Desktop.
Functions: `lens`

Functions: lens

A compilation of my favorite functions and patterns in functional programming

Lenses are difficult to articulate. More challenging is presenting a strong use-case. Perhaps you've already read up on lenses a bit. Perhaps your tireless search brings you here.

How does one describe a lens?

  • A functional getter/setter pair

This is usually the first fact one gleans from all the reading. We know that provided some data structure -- say, an object -- we can:

import R from 'ramda'

// Capture the getter/setter...
const xLens = R.lens(R.prop('x'), R.assoc('x'));

// Use it to view some place in the data...
R.view(xLens, {x: 1, y: 2});            //=> 1
// Use it to set some place in the data...
R.set(xLens, 4, {x: 1, y: 2});          //=> {x: 4, y: 2}
// And use it to apply some function to some place in the data.
R.over(xLens, R.negate, {x: 1, y: 2});  //=> {x: -1, y: 2}

It's the some place in the data that's so operative. Why? Because:

  • Lenses describe the where -- yet they need not reference any data in particular.
  • They can also describe the shape of data. We can use them as a roadmap for performing complex operations on any valid data structure we pass.

Once more:

  • Lenses exist independently of the data they may be used to transform.

Such enables lenses to be highly declarative. They describe where to focus without any knowledge of what.

Example: increment wages

For my example, I'll employ Partial Lenses, as this is the richest optics library I've yet encountered for JavaScript.

Say we've some deeply nested wage information spread across various cities:

const wages = {
  byCity: {
    richmond: [{ userId: 1, hourlyWage: 15 }],
    newOrleans: [
      { userId: 1, hourlyWage: 15 },
      { userId: 2, hourlyWage: 21 }
    ]
  }
};

Our goal is to enumerate through wages, incremement each of the wages across all cities -- without mutating wages.

It is absolutely possible to acheive as much by way of reduce and map. Believe me, we do not want to:

import R from 'ramda'
import {map, reduce} from 'lodash'

const incrementWages = data =>
  reduce(
    data.byCity,
    (acc, curr, key) => ({
      ...acc,
      byCity: {
        ...acc.byCity,
        [key]: map(
          item => ({ ...item, hourlyWage: R.inc(item.hourlyWage) }),
          curr
        )
      }
    }),
    data
  );

Enter lenses. Behold:

import * as L from 'partial.lenses'

const incrementWages = data =>
  L.modify(['byCity', L.children, L.elems, 'hourlyWage'], R.inc, data);

A function call of the variety:

incrementWages(wages)

Will yield:

Object {
  byCity: {
    richmond: [{ userId: 1, hourlyWage: 16 }],
    newOrleans: [
      { userId: 1, hourlyWage: 16 },
      { userId: 2, hourlyWage: 22 }
    ]
  }
};

Further reading

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment