Composition and Lodash FP

The second in a three part series about Lodash. This part talks about a neat way of writing clean code and a module of Lodash called FP.

Composition and Lodash FP

Welcome to the second part of the three part article series on Lodash. In the previous article, we discussed what Lodash is and its pros & cons.

If you missed it, read this before continuing. If you know about it already, read on!

Functional programming is a weird and sometimes unintuitive paradigm. It’s become fairly well known with libraries like React and Redux popularizing it in the JavaScript community, and one could say it was already well supported by JS, but never used as widely as OOP, until recently.

This article aims to introduce beginners to some practices that are used commonly with React and Redux, such as composition, higher order functions and currying. Later, it introduces a module in the Lodash library called Lodash.fp, which is designed with these exact concepts in mind.

Composition

Building apps is hard. It's easy to write buggy code. Functional programming tries to alleviate some of the common problems with writing large applications. It helps do so by relying on some core concepts, which I'll try to explain in simple words:

  1. Pure functions: Functions that always return the same output for a given input, have no side effects and are predictable.
  2. First class functions: Functions in a programming language must be like any other value, and can be passed around as arguments or outputs of other functions.
  3. Immutability: Data is just data. Values are like unchanging snapshots of real world things and functions define how the thing represented by the value changes over time.
  4. Composition: it is always possible to start off small and build complex things out of it.

Composition is about starting off with small building blocks and combining them to build big, complex things.

A common way to define these building blocks is with functions. I know you probably know what a function is, but let's have a visual notation that is hopefully obvious and at the same time simplifies further explanations.

Let's say you have a function which does nothing more than add two numbers.

In JavaScript, you could write it like:

function add(a, b) {
  return a + b
}

If we draw this with blocks, we could make something like this...

add always takes two arguments and returns one value.

Let's see if it complies with our principles of FP.

✅ It is pure, since, for example, add(2, 3) is always 5.

✅ In JS, it is first class anyway since we can pass it to other functions.

✅ It also treats data as immutable since it isn't modifying its parameters, instead producing a new value, which is a sum of the two.

So far, we're on the right track with programming functionally, but what we'll see next is interesting applications of the fourth concept, composition.

But first, we need to know what a higher order function is.

A higher order function, can take functions as arguments, or even return them. For example, Lodash.map takes an array and a function and returns an array with the function applied to each element.

Something like:

const xs = [1, 2, 3, 4]

function addOne(x) {
  return x + 1
}

const ys = _.map(xs, addOne)
//-> [2, 3, 4, 5]

As blocks, we could draw it like this...

So map takes a whole function as an argument. That's why we can call it a higher order function.

With higher order functions, we now have half the things we need to master composition. The other is something known as currying.

Currying

Well, not that kind of curry.

Currying is a technique used to break down functions in order to make them more reusable. It's named after the logician Haskell Curry, who has something named after his first name as well.

Currying involves doing the following:

  1. Take an ordinary function f.
  2. Rewrite the function as f0 that takes the first argument of f and returns f1, which takes the rest of the arguments.
  3. Repeat step 2 on f1.
  4. Keep doing this until the function you are left with takes only one argument.

The curried version of our add function would therefore look something like this:

function add(a) {
  return function (b) {
    return a + b
  }
}

The magic of closures ensures that a is defined inside the returned function.

In terms of blocks, the new curried version of add looks like this:

Hmm.. so now that add gives us a function, what do we do with it?

Well, the returned function takes the second argument. So we should just call it.

In terms of code, calling add would now look like this:

add(2)(3)
//-> 5

Similarly, if add took three instead of two arguments, it would look like this:

// Non-curried
function add(a, b, c) {
  return a + b + c
}

add(1, 2, 3)
//-> 6

// Curried
function addd(a) {
  return function (b) {
    return function (c) {
      return a + b + c
    }
  }
}

addd(1)(2)(3)
//-> 6

At this point, odds are that you're scrolling up to see who wrote this because it obviously is a crazy person. Why in the world would you do such a roundabout thing when calling add with arguments separated by commas was perfectly simple and reasonable?

This is because currying gives us a secret weapon that some functional languages have had by default for years: partial application of functions.

This allows you to create a function like addTwo which takes one argument and adds two to it.

// ES6 arrows give us a much nicer way to express curried functions
const add = (a) => (b) => a + b

add(2)(3)
//-> 5

// A partially applied function that always adds 2
const addTwo = add(2)

addTwo(5)
//-> 7

I'll admit that this example is contrived. So let's have a look at a more practical example.

The popular state management library Redux comes with bindings for React. The library is creatively named react-redux.

react-redux contains a function called connect, which is curried.

// This is a presentational component
// whose looks and behaviour depend solely on its props
const TodoList = ({ list }) => {
  return (
    <>
       {list.map(item =>
         <TodoListItem
           key={item.id}
           text={item.text}
           done={item.done}
         />
       )}
    </>
  )
}

// mapStateToProps takes the Redux store state
// and maps that to the props of some component
function mapStateToProps(state) {
  return {
    list: state.list
  }
}

// connect decides which component gets the props mapped by mapStateToProps
// connect is curried
export default connect(mapStateToProps)(TodoList)
// ?!

So... why is connect curried again?

Couple of reasons.

  1. connect called with a particular mapStateToProps can be used to wrap multiple components, if those components have the same props.
  2. connect can take a second argument, mapDispatchToProps, so by passing the component in a completely separate set of parentheses, you don't have to worry about this one being present or not. (Although it does become a little awkward if mapStateToProps doesn't need to be passed).

 

The title said Lodash. Where's Lodash?

Hold your horses, I'm getting there.

Lodash is a cleverly-put-together library that focuses quite heavily on being functional. But a lesser known fact is that it has a module called fp, which can be used to program in a truly functional style.

Almost all functions in Lodash.fp are counterparts of the regular Lodash functions. The difference is that they are curried and take their arguments in the reverse order.

Why data last? It has to do with the fact that it makes composition easier.

In the example above, fp.map partially applied to firstCaps lets you apply it to any array of strings to capitalise the first letter of each element. firstCapsAnything is a maintainable building block. You could do this with regular map too but it wouldn't be so clean.

In an earlier article, I talked about Lodash and a useful utility called chain, which lets us apply sequences of operations to data with ease. Like this:

import _ from 'lodash'

// ...

const uniqueCities = _.chain(customers)
  .filter(c => c.age >= 21 && c.age <= 30)
  .map('address.city')
  .uniq()
  .value()

But using chain comes with some problems:

  1. chain wraps the data inside a Lodash-specific object, and can only be unwrapped by calling .value on it.
  2. Importing chain effectively imports all of Lodash, causing large bundle sizes, even after applying build optimisations.
  3. chain is difficult to extend, unless you use some special functions.

Lodash.fp has a remedy. It provides a function called flow that does something similar.

What does flow look like? Well if you were to rewrite the above code snippet using flow instead of chain, it would look like this:

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

// ...

const uniqueCities = flow(
  filter(c => c.age >= 21 && c.age <= 30),
  map('address.city'),
  uniq,
)(customers)

So flow first takes a sequence of functions as arguments, then the data that those functions are to be applied to and finally applies those functions in the order provided.

It's interesting to note that each of the functions in the first set of arguments passed to flow are all partially applied. filter takes a function, then an array. map does the same, and uniq takes just an array, but what is passed to flow are partially applied versions of the same.

flow overcomes all three shortcomings of chain:

  1. It does not wrap the data in anything. What is returned is the actual result.
  2. Importing flow imports just flow.
  3. You can pass any partially applied function to flow, not just Lodash functions, provided it makes sense logically.

What this shows is that the combination of two simple ideas:

By currying together higher order functions and partially applying functions, we get a powerful mechanism that is just as good, if not better, than the magical workings of something like chain.

The Takeaway ??

We looked at some of the things that make a programming style 'functional'. We saw what partial application and currying are and supplemented them with some examples of how they can be used to compose smaller building block functions into more complex logic.

Hopefully, I've been able to help you appreciate how these simple ideas can go a long way in dealing with complexity and writing clean code.

Notes and further reading

  • Currying isn't the only way partial application can be achieved in JavaScript.
  • Ramda is a more hardcore version of Lodash.fp with a strong focus on functional programming.
  • Plain old Lodash has its own version of flow too.

Thank you for reading.

2020 © All rights reserved. GeekyAnts India Pvt Ltd.