Reading JS ... currying for newbie

My brain is exploding again ... this time it's the concept of currying
function curried_add(a) {
// has access to the argument for a
return function nested_add(b) {
// has access to the arguments for a and b
return a + b;
}
}

// creates a local variable a and assigns it the value 1
let add_one = curried_add(1);

// add_one() still has access to the argument from curried_add()
add_one(10);
function curried_add(a) {
// has access to the argument for a
return function nested_add(b) {
// has access to the arguments for a and b
return a + b;
}
}

// creates a local variable a and assigns it the value 1
let add_one = curried_add(1);

// add_one() still has access to the argument from curried_add()
add_one(10);
Where do I start to read this code? From add-one or from let add-one?
15 Replies
Joao
Joao16mo ago
Code gets executed from top to bottom so that's the right order here as well. The function curried_add accepts a single argument and immediately returns another function, defined inline. So add_one in this case is is a function itself. However the concept of currying is actually different:
function add(a) {
return function(b) {
return a + b;
}
}

// Since add returns a function, we can invoke it immediately.
const result = add(2)(3);

console.log(result); // 5
function add(a) {
return function(b) {
return a + b;
}
}

// Since add returns a function, we can invoke it immediately.
const result = add(2)(3);

console.log(result); // 5
The idea is that you can invoke a function that itself returns another function, and you can go deeper into this concept, and provide the arguments to each individual function as they are being invoked inline.
Å Marlon G
Å Marlon G16mo ago
codecademy presents it also as a safeguard for not forgetting to enter a value for the parameter ... Why is yours so much easier to understand than theirs? I'v never seen the double parenthesis syntax before ...
Joao
Joao16mo ago
Well I wouldn't say much easier but thanks! And that's actually a very valid use case of currying. The thing with JavaScript is that you are not actually forced to provide all arguments to a function, and so you may forget or switch the order of parameters to functions that have multiple. Of course when you run the code you may get an error, but sometimes falsy values and numbers can work together and won't fail, but silently introduce a bug in the code program. Another thing from the example in codecademy is that the nested function is named whereas I didn't use one, since we won't really be referencing it at all. But I think they did it this way just to make it clear that the parent function has inner functions defined within it. https://javascript.info/currying-partials
Å Marlon G
Å Marlon G16mo ago
But still, you need to explain the double parenthesis. It's the first time I see it. Could the codecademy example do the same on let add_one?
Joao
Joao16mo ago
Well, the add_one is a variable that stores the returned value of curried_add. If we apply currying it would instead store the value of whatever the inner function (aka nest_add) returns.
function curried_add(a) {
// has access to the argument for a
return function nested_add(b) {
// has access to the arguments for a and b
return a + b;
}
}

// creates a local variable a and assigns it the value 1
let add_one = curried_add(1)(10);

// add_one() still has access to the argument from curried_add()
// add_one(10);
function curried_add(a) {
// has access to the argument for a
return function nested_add(b) {
// has access to the arguments for a and b
return a + b;
}
}

// creates a local variable a and assigns it the value 1
let add_one = curried_add(1)(10);

// add_one() still has access to the argument from curried_add()
// add_one(10);
add_one is not a function anymore, but a number: 1 + 10 So, if you think about it, a () means to invoke a function right? And curried_add is invoked, and returns a function, which we are immediately invoking with another set of (). Here's another example, let's say we have a function that creates a box:
function createBox(x, y, width, height, size, color) {
// create the box here
}

const box = createBox(100, 100, 25, 50, 5, 'red');
function createBox(x, y, width, height, size, color) {
// create the box here
}

const box = createBox(100, 100, 25, 50, 5, 'red');
Without looking at the definition you may forget how many or in which order to enter the arguments. Now, let's make this same function but using currying:
function createBox(x) {
return function(y) {
return function(width) {
return function(height) {
return function(size) {
return function(color) {
// create the box here
}
}
}
}
}
}

const box = createBox(100)(100)(25)(50)(5)('red');
function createBox(x) {
return function(y) {
return function(width) {
return function(height) {
return function(size) {
return function(color) {
// create the box here
}
}
}
}
}
}

const box = createBox(100)(100)(25)(50)(5)('red');
To be honest, I rarely if ever use currying. But this is the concept: break down a function that takes multiple arguments into many multiple functions that each take exactly one. And btw this only works because of closures, where the inner function have access to the variables on their respective parent scope. So the last function that accepts the color string knows there's a variable called width in it's current scope.
Å Marlon G
Å Marlon G16mo ago
... soooo.... Regarding this you explain here: let add_one invokes the curried_add function with the value 1 for (a) and value 10 for (b). Is value (a) implicidly saved because at the end of the curried_add function it return a + b;?
Joao
Joao16mo ago
Yes, so, whenever a function runs it creates a so called execution context, a place where it stores it's own internal variables like a in this case. If you define a function in this context, it will have access to this execution context, in addition to creating its own which further function beneath it can also access. This explains why the final statement return a + b works. But the syntax (1)(10) is explained by the fact that the first function, in this case curried_add returns a function itself. By using this syntax you are immediately invoking it, before it stores whatever the return value is in the add_one variable.
Å Marlon G
Å Marlon G16mo ago
... ah, ok... soooooo This let add_one = curried_add(1)(10); is saying invoke the first function with the first value (a is stored, but not returned), and return a function that uses the second value (b, but doesn't return it). Which is why when loging a immediately after function curried_add(a) it prints 1, and b after function nested_add(b) prints 10, but they are not returned (as in can't be calculated) before we explicitly ask them to be returned with return a + b;
Joao
Joao16mo ago
That's about how it goes yes. I wouldn't think of it in terms of "storing" anything as these are just regular function being invoked. If you read the code step by step you notice that curried_add just returns a function. It just so happens that the value returned is also a function. You can store those values in separate variables if you want, there's nothing wrong or against that, but you can invoke them at the same time and store the final result. Let me give another example, let's say you have a series of items that have a price and are taxed differently depending on the type of article. You can create a function that calculates the total value using currying like this:
function calculateTax(taxPercent) {
return function(price) {
return price + price * taxPercent * 0.01;
}
}

const items = [
{
name: 'Chips',
price: 1,
tax: 10
},
{
name: 'Smartphone',
price: 200,
tax: 21
},
{
name: 'Chair',
price: 99,
tax: 15
}
];

items.forEach(item => {
const finalPrice = calculateTax(item.tax)(item.price);
console.log(finalPrice);
});
function calculateTax(taxPercent) {
return function(price) {
return price + price * taxPercent * 0.01;
}
}

const items = [
{
name: 'Chips',
price: 1,
tax: 10
},
{
name: 'Smartphone',
price: 200,
tax: 21
},
{
name: 'Chair',
price: 99,
tax: 15
}
];

items.forEach(item => {
const finalPrice = calculateTax(item.tax)(item.price);
console.log(finalPrice);
});
Å Marlon G
Å Marlon G16mo ago
So for chips, this evaluates to: item.tax is used for taxPercent, item.price is used for price, which means the final return for chips is: 1 + 1 * 10 * 0.01 = 1,1 .... 🤯
Joao
Joao16mo ago
Did I get that formula right? 🤔 Well that's not the point 😄 the idea is that you can compose behavior using functions that would otherwise take several arguments. In a more realistic scenario you would store the first function externally, so that items that have the same tax base would reuse that function.
Å Marlon G
Å Marlon G16mo ago
Well, it kind of doesn't matter, because I'm just tracing the values through the function. But this then goes back to my initial question of how to read syntax. Because one invokes the function after writing the function, which means writing at the bottom, looking back up, and then retracing the code. Your example really made it simpler to understand.So even if currying isn't that much used, this has helped me read code more consistently.
Joao
Joao16mo ago
Well I guess how to read it depends on how it works best for you. I guess ultimately it's very similar to how a regular function with multiple parameters work, it's just a practical difference.
Å Marlon G
Å Marlon G16mo ago
I do like a logical closure. I was worried I was getting lost in the world of currying syntax, but then I realised it helped me read easier. So for that, you get todays peacock. 🦚 Thank you again ... I might be back with more head scratching! 🤯
Joao
Joao16mo ago
Sure and good luck with that, its always good exercise! 👍
Want results from more Discord servers?
Add your server