- Understanding Currying in JavaScript
- Chidume Nnamdi
- Translation from: Aliyun Translation Group
- Text link: github.com/dawn-plex/t…
- Translator: Lingnuma
- Proofreader: Yishu
Understand the Currization of JavaScript
Functional programming is a style of programming that passes functions as arguments and returns functions that have no side effects (changing the state of the program)
Many computer languages have adopted this programming style. Of these, JavaScript, Haskell, Clojure, Erlang, and Scala are the most popular.
Due to this style’s ability to pass and return functions, it introduces a number of concepts:
- Pure functions
- Currie,
- Higher-order functions
The concept that we’re going to talk about is the Currization of this.
In this article π, we’ll see how cremation works and how it can be put into practice by software developers.
Tip: Instead of copying and pasting, you can use Bit to convert reusable JavaScript functionality into components that can be quickly shared with your team across projects.
What is Corrification?
Currization is the process of functional programming in which a function with multiple arguments is converted into a series of nested functions. It returns a new function that expects to pass in the next argument.
It keeps returning the new function (which, as we said earlier, expects the current arguments) until all the arguments have been used. The arguments remain alive (via closures) and are used for execution when the last function in the Corrified function chain is returned and called.
Currization is the process of converting a function that has more arity to one that has fewer – Kristina Brainwave
Note: The term arity above refers to the number of arguments to a function. So for example,
function fn(a, b)
//...
}
function _fn(a, b, c) {
//...
}
Copy the code
The function fn takes two arguments (a 2-arity function) and _fn takes three arguments (a 3-arity function).
So, Corrification transforms a multi-parameter function into a series of functions that take only one parameter.
Let’s look at a simple example:
function multiply(a, b, c) {
return a * b * c;
}
Copy the code
This function takes three numbers, multiplies the numbers and returns the result.
Multiply (1, 2, 3); / / 6Copy the code
You see how we call the multiplication function with the full argument. Let’s create a Currie version and see how we can call the same function (and get the same result) in a series of calls:
function multiply(a) {
return (b) => {
return (c) => {
return a * b * c
}
}
}
log(multiply(1)(2)(3)) // 6
Copy the code
We have converted multiply(1,2,3) function calls to multiple multiply(1)(2)(3) function calls.
A single function has been converted to a series of functions. To get the result of the numbers 1, 2, and 3, these arguments are passed one after the other, each number prepassed to the next function for internal invocation.
We can split the multiply(1)(2)(3) to better understand it:
const mul1 = multiply(1);
const mul2 = mul1(2);
const result = mul2(3);
log(result); / / 6Copy the code
Let’s call them in turn. We pass 1 to the multiply function:
let mul1 = multiply(1);
Copy the code
It returns this function:
return (b) => {
return (c) => {
return a * b * c
}
}
Copy the code
Now, mul1 holds the above function definition, which takes a parameter b.
We call the mul1 function, passing 2:
let mul2 = mul1(2);
Copy the code
Num1 returns the third argument:
return (c) => {
return a * b * c
}
Copy the code
The returned parameter is now stored in the variable mul2.
Mul2 will become:
mul2 = (c) => {
return a * b * c
}
Copy the code
When you pass parameter 3 to function mul2 and call it,
const result = mul2(3);
Copy the code
It evaluates with the arguments a = 1 and b = 2 passed in earlier and returns 6.
log(result); / / 6Copy the code
As a nested function, mul2 has access to the variable scope of the external function.
This is how mul2 can add with variables defined in the exited function. Although these functions return long ago and are garbage collected from memory, their variables remain alive.
You will see that the three numbers are applied one after the other to the function call, and each time a new function is returned until all the numbers have been applied.
Let’s look at another example:
function volume(l,w,h) {
return l * w * h;
}
const aCylinder = volume(100,20,90) // 180000
Copy the code
We have a function called volume to calculate the volume of any solid shape.
The currified version will take an argument and return a function. The new function will still take an argument and return a new function. This process continues until the last parameter arrives and returns the last function, which multiplies the last parameter with the previously accepted parameter.
function volume(l) {
return (w) => {
return (h) => {
return l * w * h
}
}
}
const aCylinder = volume(100)(20)(90) // 180000
Copy the code
As we did in multiply, the last function takes only the argument h, but does the operation using variables from other scopes that have already been returned. They still work because of closures.
The idea behind Cremation is to take a function and get a function that returns a dedicated function.
Currization in mathematics
I prefer the mathematical illustration πWikipedia, which further demonstrates the concept of Currization. Let’s look at our own example.
Suppose we have an equation:
f(x,y) = x^2 + y = z
Copy the code
There are two variables x and y. If these two variables are assigned, x=3, y=4, and finally the value of z.
: If we assign 4 to y and 3 to x in method f(z,y),
F (x,y) = f(3,4) = x^2 + y = 3^2 + 4 = 13 = zCopy the code
We’ll get to 13.
We can currize f(x,y) into a set of functions:
h = x^2 + y = f(x,y)
hy(x) = x^2 + y = hx(y) = x^2 + y
[hx => w.r.t x] and [hy => w.r.t y]
Copy the code
Note: hx, x is the subscript of h; Hy, y is the subscript of h.
If we set x=3 in the equation hx(y) = x^2 + y, it returns a new equation with a variable y:
h3(y) = 3^2 + y = 9 + y
Note: h3 is h subscript 3
Copy the code
It is the same as the following:
h3(y) = h(3)(y) = f(3,y) = 3^2 + y = 9 + y
Copy the code
This value is not evaluated, it returns a new equation 9 + y, which accepts another variable, y.
Next, we set y=4:
H3 (4) = h(3)(4) = f(3,4) = 9 + 4 = 13Copy the code
Y is the last variable in the chain, and the addition operation operates on it and the remaining previous variable, x = 3, to get 13.
Basically, we currize this equation, dividing f(x,y) = 3^2 + y into a system of equations:
3 ^ 2 + y - > 9 + y f (3, y) = h3 (y) = 3 ^ 2 + y = 9 + y f (3, y) = 9 + y f (3, 4) = h3 (4) = 9 + 4 = 13Copy the code
Before we finally get the results.
Wow!!!!! Here are some math problems, if you’re not feeling clear π. Full details can be found at Wikipedia π.
Currization and partial function applications
Now, some people might start to think that the number of nested functions that a Currified function has depends on the number of arguments it depends on. Yeah, that’s what makes it currified.
I designed a Currified function to find volume:
function volume(l) {
return (w, h) => {
return l * w * h
}
}
Copy the code
We can call L as follows:
const hCy = volume(70);
hCy(203,142);
hCy(220,122);
hCy(120,123);
Copy the code
or
Volume (70) (90, 30); Volume (70) (390320); Volume (70) (940340);Copy the code
We define a function for calculating the volume (L) of a cylinder of any length, 70.
It has three arguments and two nested functions. Unlike our previous version, there were three arguments and three nested functions.
This is not a Currified version. We’ve just done a partial application of the volume function.
Currization and partial application are similar, but they are different concepts.
Some applications convert one function to another smaller function.
function acidityRatio(x, y, z) {
return performOp(x,y,z)
}
|
V
function acidityRatio(x) {
return (y,z) => {
return performOp(x,y,z)
}
}
Copy the code
Note: I have deliberately omitted the implementation of the performOp function. In this case, it’s not necessary. You just need to know the concept behind currization and partial application.
This is part of the application of the acidityRatio function. There is no corrification involved. AcidityRatio is partially applied and is expected to take fewer arguments than the original function.
Make it Currified, and it will look like this:
function acidityRatio(x) {
return (y) = > {
return (z) = > {
return performOp(x,y,z)
}
}
}
Copy the code
Cremation creates nested functions based on the number of arguments to the function. Each function takes one argument. If there are no parameters, it’s not Currization.
Currization works on functions with more than two parameters – Wikipedia
Corrification transforms a function into a series of functions that take a single argument. ,
Here is an example of the same kind of Currization and partial application. Suppose we have a function:
function div(x,y) {
return x/y;
}
Copy the code
If we partially apply this function. Will get:
function div(x) {
return (y) => {
returnx/y; }}Copy the code
And, again, the Same result will come out:
function div(x) {
return (y) => {
returnx/y; }}Copy the code
Although currization and partial application yield the same results, they are two completely different concepts.
As we said before, Currization and partial application are similar, but the actual definition is different. What they have in common is dependency closures.
Does corrification work?
Of course, if you want it, you can have it:
1. Write small modules of code that can be easily reused and configured, as NPM does:
Let’s say you have a store π and you want to give your customers a 10% discount:
function discount(price, discount) {
return price * discount
}
Copy the code
When a valued customer buys a $500 item, you give him:
Const price = discount,0.10 (500); //$50
// The $500 - $50 = The $450
Copy the code
You’ll find that in the long run, we calculate our own 10% discount every day.
Const price = discount,0.10 (1500); //The $150
// The $1The 500 -The $150 = The $1,350
const price = discount(2000,0.10); // The $200
// $2The 000 -The $200 = The $1,800
const price = discount(50,0.10); // A $5
// $50 - A $5 = $45Const price = discount,0.10 (5000); //The $500
// A $5The 000 -The $500 = $4,500
const price = discount(300,0.10); // $30
// The $300 - $30 = The $270
Copy the code
We can curryize the discount function so that we don’t need to add 0.10 discounts every day:
function discount(discount) {
return (price) => {
returnprice * discount; }} const tenPercentDiscount = discount(0.1);Copy the code
Now we can do the math using only the prices of items purchased by your valued customers:
tenPercentDiscount(500); // $50
// The $500 - $50 = The $450
Copy the code
Again, it happens that some valuable customers are more important than others — we call them super value customers. And we want to give super value customers a 20% discount.
We use the currified discount function:
Const twentyPercentDiscount = discount (0.2);Copy the code
We set up a new function for the super value customer that calls the Coriolization function that accepts a discount value of 0.2.
The function returned twentyPercentDiscount will be used to calculate the super value customer discount:
twentyPercentDiscount(500); / / / / 100The $500 - The $100 = The $400twentyPercentDiscount(5000); / / / / 1000A $5The 000 -The $1, 000 =$4,000 twentyPercentDiscount(1000000); / / / / 200000The $1The 000000 -The $200, 000 =The $600, 000,Copy the code
2. Avoid frequent calls to functions with the same parameters:
For example, we have a function to calculate the volume of a cylinder:
function volume(l, w, h) {
return l * w * h;
}
Copy the code
As it happens, all the cylinders in your warehouse are 100 meters high. You’ll find that you’ll repeatedly call functions that take arguments of height 100:
Volume (200,30,100) / / 2003000 l volume (32,45,100); / / / / 144000 l volume (2322232100) 53870400 lCopy the code
To solve this problem, we need to currize the function that computs volume (as we did before) :
function volume(h) {
return (w) => {
return (l) => {
return l * w * h
}
}
}
Copy the code
We can define a specific function that computes the height of a particular cylinder:
const hCylinderHeight = volume(100); hCylinderHeight(200)(30); / / 600000 l hCylinderHeight (2322) (232); / / 53870400 lCopy the code
General Currization functions
Let’s develop a function that takes any function and returns a Currified version of the function.
To do this, we need this (although you use a different method yourself than MINE) :
functioncurry(fn, ... args) {return(... _arg) => {returnfn(... args, ... _arg); }}Copy the code
What did we do here? Our Currization function takes a function (fn) that we want to currize, and a set of arguments (… The args). The extension operator is used to collect arguments after fn to… The args.
Next, we return a function that also collects the remaining arguments as… _args. This function will… Args passes in the original function fn and calls it, using the extension operator to… _args is also passed in as an argument, and the resulting value is then returned to the user.
Now we can use our own Curry function to create specialized functions.
Let’s use our own Coriolization function to create more specialized functions (one of which is a method for calculating the volume of a cylinder at 100m height)
function volume(l,h,w) {
returnl * h * w } const hCy = curry(volume,100); HCy (200900); / / 18000000 l hCy (70 '); // 420000lCopy the code
conclusion
Closures make currization possible in JavaScript. It holds the state of functions that have already been executed, allowing us to create factory functions – functions that we can add specific parameters to.
It’s hard to fill your head with Curryification, closures, and functional programming. But I promise you, with time and everyday application, you’ll get the hang of it and see the value π.
reference
π Currying – Wikipedia
π Partial Application Function – Wikipedia