Let's talk about Curry.... no, not Steph or the food.
Marc Lawingco
Posted on June 30, 2021
Have you ever gone to a interview and interviewer ask you a question. "Do you know what currying is for?" and you gulp your guts, palms are sweaty, arms weak and knees got heavy.
The first thing that comes into your mind are either the greatest 3 point shooter ever or the food that Japanese loves a lot. Well that maybe that just for me I guess, But even if you know what currying is you can't somehow think of anything to use it for that make sense in realms of Javascript.
Okay but first, what in the world is currying?
For starters, Currying is a process of converting multiple arguments function into a series of nested single argument function, in order words Its processing arguments once at a time. Its also worth noting that currying is not calling a function within a function, it just transform it to form a single one.
Think of it as your wife, telling you each one of all of your mistakes through out the years in an argument
versus
Just calling out your whole name including your middle one, The latter for sure will give you instant flashbacks of all of your mistakes, That's for sure.
Joking aside
There are few concepts to know about currying, Closures, Higher Order Function and Partially applied function.
Closures
Like any argument you have with your wife, you also need closure for currying too!. Closure in a nutshell can be summarized with a cheesy saying, "Its not you, its me" but change it a bit into "Its not that, Its this". Because scopes are separated to each other and to at a reading point to global too.
Take a look at the example here
function jumanjiPlayer(playerName){
let position = 0;
return function rollADice(){
position = position + Math.floor(Math.random() * 6) + 1
return `${playerName} is now on position: ${position}`;
}
}
const allanTurn = jumanjiPlayer('Allan');
const peterTurn = jumanjiPlayer('Peter');
const judyTurn = jumanjiPlayer('Judy');
console.log(allanTurn()); // "Allan is now on position: 4"
console.log(peterTurn()); // "Peter is now on position: 4"
console.log(judyTurn()); // "Judy is now on position: 1"
console.log(allanTurn()); // "Allan is now on position: 9"
console.log(peterTurn()); // "Peter is now on position: 7"
console.log(judyTurn()); // "Judy is now on position: 5"
Notice how we can keep track the value of position easily?
Closures are pretty useful in setting up and persisting local environments, which in turn you can gain few benefits such as non pollution of global scope, privatizing the value of position so we can limit the user to change this value (I'd argue its still possible but at least harder) and etc which is not the main point of this post.
Okaayy, why closure relates to Currying ?
Well because
console.log(allanTurn());
can be considered as currying too which in fact just the same as
console.log(jumanjiPlayer('Allan')());
And currying is just chaining up multiple closures and returning single evaluated function to the user.
But this is not ideal example to show what currying is, without a real world example, we can only get it as a concept rather than its real world applications.
Let's take another aspect of curry
Higher Order function
Using HoF itself doesn't always means you are currying, it's nature is just a function that accepts function as a parameter or may either return a function or not. In real world you may have use HoF already without knowing each time you use any of javascript array or everytime you try to use callbacks
For example, we can visualize the closure example we have earlier into something like this.
function player(fn, ...args){
let position = 0;
return function (){
position = position + Math.floor(Math.random() * 6) + 1
return fn(...args, position);
}
}
function newJumanjiPlayer(playerName, position) {
return `${playerName} is now on position: ${position}`;
}
const allanTurn = player(newJumanjiPlayer, 'Allan');
const peterTurn = player(newJumanjiPlayer, 'Peter');
const judyTurn = player(newJumanjiPlayer, 'Judy');
console.log(allanTurn()); // "Allan is now on position: 4"
console.log(peterTurn()); // "Peter is now on position: 4"
console.log(judyTurn()); // "Judy is now on position: 1"
console.log(allanTurn()); // "Allan is now on position: 9"
console.log(peterTurn()); // "Peter is now on position: 7"
console.log(judyTurn()); // "Judy is now on position: 5"
As you can see we can now use newJumanjiPlayer
to hook our codes in a much more friendly manner.
So far so good right? Where is currying though?
So let's say we just don't want to randomize the position movement but we want to add it as parameter, we also want to explicitly tell the movement as an optional. We can tweak the code above with this.
function player(fn, ...args){
let position = 0;
return function (...restArgs){
const toAdd = restArgs.length > 0 ? [...restArgs].reduce((a, b) => a + b, 0): Math.floor(Math.random() * 6) + 1;
position = position + toAdd;
return fn(...args, position);
}
}
function newJumanjiPlayer(playerName, position) {
return `${playerName} is now on position: ${position}`;
}
With this we can keep our random 1 to 6 behavior while also able to put exact movement.
const allanTurn = player(newJumanjiPlayer, 'Allan');
const peterTurn = player(newJumanjiPlayer, 'Peter');
const judyTurn = player(newJumanjiPlayer, 'Judy');
console.log(allanTurn(5,3,2,1)); // "Allan is now on position: 11"
console.log(peterTurn(1)); // "Peter is now on position: 1"
console.log(judyTurn());
console.log(allanTurn());
console.log(peterTurn());
console.log(judyTurn());
Fancy right? Currying make things abstracted and reusable
Practical Uses?
In the end currying is just a sugary syntax, you can follow but there are few things this pattern shines.
Performance gains on reusable functions shines with currying
Take a look at this code
const [todos, setTodos] = useState([]);
useEffect(() => {
(async function () {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos"
);
const list = await response.json();
setTodos(list);
})();
}, []);
const userTodos = (list) => (userId) => {
console.log("this will not run everytime we click the button");
const userTodoList = list.filter((item) => item.userId === userId);
return (completed) => {
console.log("this will run everytime we click the button");
return userTodoList.filter((item) => item.completed === completed);
};
};
const doFilterByStatus = userTodos(todos)(1); // 1 is userId
return (
<div className="App">
<button onClick={() => console.log(doFilterByStatus(false))}>
Filter false
</button>
<button onClick={() => console.log(doFilterByStatus(true))}>
Filter true
</button>
</div>
);
https://codesandbox.io/s/my-test-tv1ol?file=/src/App.js
Normally you will just filter it in one go, but let's assume this todos
contains hundred of thousands of data, in this curried approach we are getting all of todo
of user 1
only once, and we just filter for the completed
status against those already filtered list. Imagine it on other scenarios like for example data scraping or migration of data, like in e-commerce site, get all products that has a category of FOOD
.
Concise api
Currying was a elimination of multiple arguments, or rather converting it from myFunc(a,b,c)
to become myfunc(a)(b)(c)
. This makes the code less redundant.
Afterthought
I've been reading a lot of things around functional programming this past few weeks and currying really stuck me as I used it unknowingly already. Feel free to add or point out any non accurate things I've said in this. Anyway, Currying is not required, as you can implement a lot of things without using it. In the end its just a implementation choice.
I've used it on factory functions before, but where did you actually able to use currying?
Posted on June 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.