DEV Community

Patrik Jajcay
Patrik Jajcay

Posted on • Edited on

What is Lodash/fp, even?

Hey!

This post is aimed at people who use lodash and want to try the FP variant but maybe want/need some guidance on what's the point of using the FP variant.

I will demonstrate the difference between the fp and non-fp variants using lodash _.cond for easy grasp of the topic.

At first, we will use non-fp lodash in examples.


_.cond

What is _.cond and why should I be interested?

From documentation:

Creates a function that iterates over pairs and invokes the corresponding function of the first predicate to return truthy. The predicate-function pairs are invoked with the this binding and arguments of the created function.

Key points:

  • returns function
  • pairs have to be functions
  • both functions in a pair are called with whatever you call the function returned from _.cond
  • top to bottom evaluation

_.cond is basically a glorified switch statement. It looks like this:

var getColor = _.cond([
  [checkFn1, resultProvidingFn1],
  [checkFn2, resultProvidingFn2],
  ...
])

var color = getColor(valueToCheckAgainst)
Enter fullscreen mode Exit fullscreen mode

Let's see it in real action:


var isBetween0and5 = (value) => _.inRange(value, 0, 5)
var isBetween5and10 = (value) => _.inRange(value, 5, 10)

var returnRed = () => 'red';
var returnGreen = () => 'green';
var returnBlue = () => 'blue';

var returnTrue = () => true;

var getColor = _.cond([
  [isBetween0and5, returnRed],
  [isBetween5and10, returnBlue],
  [returnTrue, returnGreen] // default case
])

var color1 = getColor(3) // red
var color2 = getColor(7) // blue
var color3 = getColor(15) // green

Enter fullscreen mode Exit fullscreen mode

Great! Everything's working! But...

There is a lot of code for such simple thing, don't you think?

Maybe until this point, it wasn't clear for you why does lodash have methods such as _.stubTrue, _.stubFalse or _.constant when, in fact, you can just type the values yourself.

But... Can you?

You see, _.cond accepts functions so putting something like [isBetween0and5, 'red'] into predicates wouldn't work.

With this in mind, the example above can be rewritten like:


var isBetween0and5 = (value) => _.inRange(value, 0, 5)
var isBetween5and10 = (value) => _.inRange(value, 5, 10)

var getColor = _.cond([
  [isBetween0and5, _.constant('red')],
  [isBetween5and10, _.constant('blue')],
  [_.stubTrue, _.constant('green')] // default case
])

var color1 = getColor(3) // red
var color2 = getColor(7) // blue
var color3 = getColor(15) // green

Enter fullscreen mode Exit fullscreen mode

That's better! But...

If you look at these functions:

var isBetween0and5 = (value) => _.inRange(value, 0, 5)
var isBetween5and10 = (value) => _.inRange(value, 5, 10)
Enter fullscreen mode Exit fullscreen mode

You can see they are basically just returning the result from _.inRange. Maybe we can use it directly?

Great, so you start typing this:

var getColor = _.cond([
  [_.inRange(value, 0, 5), _.constant('red')]
])
Enter fullscreen mode Exit fullscreen mode

...when you come to realise that there is no value in scope you could use.

So now you're thinking: "I just remove the value argument and it will pass it down anyway!"

Which is true, the value would get passed down, except...

You already invoked the function using ().

This would crash because _.cond would expect a function where you provided a value (by calling the function).

Okay hold on... so how to actually accomplish this without the wrapper function?

There are 2 ways:

_.curry

_.curry is a method that takes a function and curries it. For those who don't know what currying is, it's basically this:


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

add(2)(3) // 5

Enter fullscreen mode Exit fullscreen mode

Curried function is a function that accepts N arguments and won't give you result without providing N arguments - instead, it will return another function that accepts the rest of arguments.

Let's revisit our code once again:


var curriedInRange = _.curry(_.inRange)

var getColor = _.cond([
  [curriedInRange(_, 0, 5), _.constant('red')],
  [curriedInRange(_, 5, 10), _.constant('blue')],
  [_.stubTrue, _.constant('green')] // default case
])

var color1 = getColor(3) // red
var color2 = getColor(7) // blue
var color3 = getColor(15) // green

Enter fullscreen mode Exit fullscreen mode

Looking good! But why do we have to use that lodash placeholder (_) in first argument for curriedInRange ?

The problem is, non-fp lodash methods do not follow the iteratee-first, data-last pattern which is required in functional programming (it means that data are the last argument to the function).

So... what this placeholder inside curried function does is basically "Okay, here is a placeholder, fill in the other arguments as they are and return me a function that will replace that placeholder with a value.". That way, we can finally get our function to use in _.cond!

Yay! Everything works! 🥳

But there is a better way:

Lodash/fp

Lodash/fp has the same functionality as non-fp lodash but its methods are all curried and follow the iteratee-first, data-last pattern.

This enables us to drop all the ceremony like before and write:


// using lodash/fp variant

var getColor = _.cond([
  [_.inRange(0, 5), _.constant('red')],
  [_.inRange(5, 10), _.constant('blue')],
  [_.stubTrue, _.constant('green')] // default case
])

var color1 = getColor(3) // red
var color2 = getColor(7) // blue
var color3 = getColor(15) // green

Enter fullscreen mode Exit fullscreen mode

Tadaaa! All working and clean.

In this last example, what happened was the following:

_.inRange(0, 5)(valueSuppliedByCond)

(remember, the _.inRange method is now curried and follows iteratee-first, data-last pattern)

Here you can see why having data last is so important - because you're not calling the functions directly, you just provide them and they get called elsewhere with some value.

This example was aimed at _.cond but it does apply everywhere in functional programming. (_.flow being a very good candidate also).

You can look up more info about lodash/fp here: https://github.com/lodash/lodash/wiki/FP-Guide

Since this is my first post here, sorry for formatting. Please leave me some feedback on what you like/dislike, or some questions regarding the subject :)

Thanks for reading! Hope you enjoyed it.

Top comments (6)

Collapse
 
mitramejia profile image
Mitra Mejia

For an slighly cleaner alternative:



import {cond, inRange, subTrue} from 'lodash/fp'

const getColor = cond([
  [inRange(0, 5), () => 'red'],
  [inRange(5, 10), () => 'blue')],
  [stubTrue, () => 'green']
])
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ifarmgolems profile image
Patrik Jajcay • Edited

I did not assume imports to be present. I guess the methods could also be destructured from _ like

const {cond, inRange, ...} = _;
...
Enter fullscreen mode Exit fullscreen mode

And using them without _. prefix.

Collapse
 
ademhodzic profile image
AdemHodzic

This has to be one of the best articles I've seen here. Awesome explanation!

Collapse
 
ifarmgolems profile image
Patrik Jajcay

Thank you!

Collapse
 
mrwensveen profile image
Matthijs Wensveen

Great article!
Somehow all lodash/fp links just redirect to the normal lodash docs, so I'm glad I could find this.

In the example of curried add return return a + b, should be return a + b;

🙂

Collapse
 
ifarmgolems profile image
Patrik Jajcay

Fixed. Thank you!