Functional composition with compose and pipe in lodash/fp

Reading Time: 3 minutes

 134 total views

What is functional composition?

Functional composition is the technique of composing multiple functions into a complex function. In Mathematic definition,

f(x) = x + 1
g(x) = x * 2
h(x) = f(g(x)) = f(x * 2) = (x * 2) + 1

f(x) and g(x) are functions and h(x) is the composed function of f and g.

Functional composition is an efficient technique in processing list data and it is preferred over calling chained array methods to achieve the same result.

The code snippet below uses Array.filter, Array.map and Array.reduce to calculate the total, 105.

const multThree = (num: number) => num * 3
const addOne = (num: number) => num + 1
const isEven = (num: number) => num % 2 === 0
const combineSum = (acc: number, num: number) => acc + num

const data = [1,2,3,4,5,6,7,8,9,10]
const total = data.filter(isEven)
 .map(addOne)
 .map(multThree)
 .reduce(combineSum)

The chained methods produces intermediate arrays before we calculate the total.

[1,2,3,4,5,6,7,8,9,10].filter(number => number % 2 === 0) => [2,4,6,8,10]
[2, 4, 6, 8, 10].map(num => num + 1) => [3,5,7,9,11]
[3,5,7,9,11].map(num => num * 3) => [9,15,21,27,33]
[9,15,21,27,33].reduce((acc, num) => acc + num, 0) => 105

Array method chaining has the following drawback:

  • Produce the side effect of intermediate arrays and can lead to memory issue with big input array
  • Developers have to trace each step to visualize the input and output of it
  • Authors think in iterative approach and not functional approach

Functional composition can solve this problem because it combines functions to create a new function. When we feed list data to the new function, it manipulates the data once and combines the new result with the previous result.

In this post, we will see how lodash/fp offers compose, pipe and the counterpart of Array.methods to build a composed function. The composed function is capable of accepting input list and returning the final output by processing the list once.

let's go

Functional composition with compose

compose executes functions from right to left direction. If the functions are f(g(x)), we call compose(g(x), f(x)) in this manner.

Let’s rewrite the above function with high order function, compose.

import { compose, map, filter, reduce } from 'lodash/fp'

const composeFunction = compose(
    reduce(combineSum),
    map(multThree), 
    map(addOne), 
    filter(isEven)
)

const total2 = composeFunction(data)
console.log('with lodash/fp compose', total2)

First, we import compose, map, filter and reduce from ‘lodash/fp’. Since compose executes from right to left direction, reduce is the first argument of compose where as filter is the last one.

In the code, we use compose to combine filter, map and reduce to build a composed function called composeFunction.

When we feed data to composedFunction, composedFunction traverses the list once to calculate the total of 105.

composedFunction([1,2,3,4,5,6,7,8,9,10])
=> 9 + 15 + 21, 27, 33 
=> 105

The benefit of composedFunction is the removal of the the side effect of producing intermediate arrays.

Functional composition with pipe

Next, we rewrite composedFunction with pipe and called it pipeFunction

Similar to compose, pipe is a high order function that flows data through functions from left to right direction.

import { pipe, map, filter, reduce } from 'lodash/fp'

const pipeFunction = pipe(
    filter(isEven),
    map(addOne), 
    map(multThree), 
    reduce(combineSum),
)

const total4 = pipeFunction(data)
console.log('with lodash/fp pipe', total4)

The arguments of pipe are reversed but pipeFunction should achieve the same result as composedFunction.

pipeFunction([1,2,3,4,5,6,7,8,9,10])
=> 9 + 15 + 21, 27, 33 
=> 105

Finally, you can run the Stackblitz demo to play with the examples of compose, pipe and native methods of Array.

Final thoughts

Function composition has these benefits:

  • Remove the side effect of producing intermediate arrays
  • Developers do not have to trace the steps to derive the output list that becomes the input list of the next chained array method
  • Codes adopt functional approach and are point-free

In conclusion, I highly recommend developers to understand and practice functional programming in JavaScript and use the tool to replace array chaining to improve the efficiency of list processing.

Resources:

  1. Functiona light JS: https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch4.md/#chapter-4-composing-functions
  2. lodash/fp guide: https://github.com/lodash/lodash/wiki/FP-Guide
  3. ramdaJs: https://ramdajs.com/docs/#compose
  4. Stackblitz: https://stackblitz.com/edit/typescript-tmi9eg

You probably don’t need Lodash in Nestjs

Reading Time: 2 minutes

 144 total views,  2 views today

I completed Angular Architect Training Course from Bonnie Brennan at Angular Nation and lesson 1 is about style and structure. Even though the tips are for Angular application but a couple of them applies to NestJS. One of them is to void fat libraries such as lodash and moment.

In this post, I am going to describe how I limited the import size and usage of lodash library in our NestJS project at work.

Lodash contains a lot of useful functions but it is a rather large library with the size of 70kb. When developers import an lodash function using ES6 import, they are in fact import the entire library into the file that is unexpected.

After reading few blog posts, I found out that both statements are equivalent and import the whole library to file

  • import _ from ‘lodash’
  • import { sort } from ‘lodash’

Even though NestJS app resides in server side and bundle size is not a criterial factor, I wish to import lodash functions that the project is using only and replace other lodash functions with native JS.

you-dont-use-lodash-underscore plugin

NestJS app uses eslint to lint files; therefore, I installed eslint plugin, eslint-plugin-you-dont-need-lodash-underscore, and extended it in eslintrc.js

npm install --save-dev eslint-plugin-you-dont-need-lodash-underscore
"extends" : ["plugin:you-dont-need-lodash-underscore/compatible"]

Execute npm run lint command on terminal and the plugin outputted lodash errors. For example, uniq, flatten and omit can be replaced with native JS.

I overrided @typescript-eslint/no-unused-vars rule such that eslint does not complain unused variable(s) when rest spread operator is used

"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }]
// Replace omit with rest spread operator
const something = omit(object, ['p1'])

const { p1, ...something } = object
// Replace uniq with Array destructuring and Set 
const u = uniq([1,2,3,1,1,1])

const u = [...new Set<number>([1,2,3,1,1,1])]
// Replace flatten with Array.flat if ES2019 is used
const f = flatten([1,[2,3], 4)

const f = [1,[2,3], 4].flat()

When all lodash errors were resolved, re-run npm run lint and it produced zero errors

Correct way to import lodash

I tried

import pick from 'lodash/pick' 

but the compiler complained. When I used

 import pick = require('lodash/pick')

it worked perfectly.

These are the steps I used to replace lodash functions with light-weight alternatives and import the rest one by one.

Resources:

  1. https://www.blazemeter.com/blog/the-correct-way-to-import-lodash-libraries-a-benchmark
  2. https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore