Functional programming with Lodash

Lodash is a utility library written for Javascript - it contains a lot of useful functions for accessing and manipulating objects, arrays and lists.

Here we're using get to safely access the properties of an object. This is equivalent to returning obj.user.name, but if obj.user is missing then it will return null rather than throwing a TypeError.

import { get } from 'lodash'

const obj = { user: { name: 'Alex' } }
get(obj, 'user.name')

Lodash exports a functional version of all their utilities as lodash/fp:

import { get } from 'lodash/fp'

const obj = { user: { name: 'Alex' } }
const getName = get('user.name')
getName(obj)

All the functions in lodash/fp are transformed like this, so that instead of calling them using func(a, b, c) you use func(c)(b)(a). (This is called currying)

There are some slight differences between lodash and lodash/fp - e.g. get from lodash can take an optional 3rd parameter, so you can choose what to return by default if the prop you're trying to access is missing. The curried fp version doesn't - it can only be called as get(prop)(obj).

This is referred to as having a fixed arity of 2 - get can only be called twice.

As with a lot of the functions in lodash/fp, with get the order of arguments is reversed - instead of get(obj, prop) it's now get(prop)(obj). This is done because the first argument is usually the data that you want to manipulate - it's easier to chain multiple functions together this way.

In this example, we've got a list of users and we'd like a list of just their name and email:

import { pick, map } from 'lodash/fp'

const users = [
  { name: 'Alex', email: 'alex@example.com', uid: '124234', userType: 'admin' },
  { name: 'Beau', email: null, userType: 'user', uid: '152335' },
  { name: 'Charlie', email: 'charlie@char.ly', userType: 'user', uid: '23221' },
  { name: 'Daytona', email: 'dave@dave.io', userType: 'user', uid: '234216' }
  // ...
]

const getUserProperties = pick(['name', 'email'])
const cleanUserList = map(getUserProperties)

cleanUserList(users)
// Returns the following:
// [
//   { name: 'Alex', email: 'alex@example.com' },
//   { name: 'Beau', email: null },
//   ...
// ]

Rather than defining functions for each intermediate step, we can use flow to combine them. In this example, we're also filtering out users without a valid email, and grouping users by whether they're an admin or a user:

import { flow, filter, map, pick, groupBy } from 'lodash/fp'

const users = [
  // as above
]

const groupUserList = flow(
  // filter users by whether email is truthy
  filter('email'),
  map(pick(['name', 'email', 'userType'])),
  groupBy('userType')
)

groupUserList(users)
// Returns the following:
// {
//   admin: [
//     { name: 'Alex', email: 'alex@example.com', userType: 'admin' }
//   ],
//   user: [
//     { name: 'Charlie', email: 'charlie@char.ly', userType: 'user' },
//     { name: 'Daytona', email: 'dave@dave.io', userType: 'user' }
//   ]
// }

You could chain the functions together yourself, however this becomes a lot harder to follow:

const groupUserList = users =>
  // 3. group
  groupBy('userType')(
    // 2. pick props
    map(pick(['name', 'email', 'userType']))(
      // 1. filter users by whether email is truthy
      filter('email')(users)
    )
  )

You can compose similar expressions like this with regular lodash using chain, however this comes with a big downside - if you use chain then you have to import the entirity of lodash. With lodash/fp and flow, module bundlers (such as Webpack) can selectively import just the parts of lodash you're using, resulting in a much smaller Javascript bundle (this is called tree shaking).

// Not recommended!
import { chain, pick } from 'lodash'

const users = [
  // as above
]

const cleanUserList = users =>
  chain(users)
    .filter('email')
    .map(user => pick(user, ['name', 'email', 'userType']))
    .groupBy('userType')
    .value()

Another example - creating a object to map from uid to name:

import { flow, map, fromPairs } from 'lodash/fp'

const users = [
  // as above
]

const uidToName = flow(
  map(user => [user.uid, user.name]),
  fromPairs
)

uidToName(users)
// Returns:
// {
//   23221: 'Charlie',
//   124234: 'Alex',
//   152335: 'Beau',
//   234216: 'Daytona'
// }

This example shows extracting a list of unique email domains for our users:

import { flow, map, filter, get, isString, uniq } from 'lodash/fp'

const users = [
  { name: 'Alex', email: 'alex@example.com', uid: '124234', userType: 'admin' },
  { name: 'Beau', email: null, userType: 'user', uid: '152335' },
  { name: 'Charlie', email: 'charlie@char.ly', userType: 'user', uid: '23221' },
  { name: 'Daytona', email: 'dave@dave.io', userType: 'user', uid: '234216' },
  { name: 'Elliott', email: 'elliott@example.com' }
  // ...
]

const getDomain = email => email.split('@')[1]

const getEmailDomains = flow(
  map(get('email')),
  filter(isString),
  map(getDomain),
  uniq
)

getEmailDomains(users)
// Returns: ["example.com", "char.ly", "dave.io"]

I hope this helps! Please feel free to get in touch on Twitter 🙂