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)
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
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
That's better! But...
If you look at these functions:
var isBetween0and5 = (value) => _.inRange(value, 0, 5)
var isBetween5and10 = (value) => _.inRange(value, 5, 10)
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')]
])
...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
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
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
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)
For an slighly cleaner alternative:
I did not assume imports to be present. I guess the methods could also be destructured from
_
likeAnd using them without
_.
prefix.This has to be one of the best articles I've seen here. Awesome explanation!
Thank you!
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 bereturn a + b;
🙂
Fixed. Thank you!