This article is a direct translation of an overseas article about Currying. All are based on the original text processing, the other are direct translation may be a little stiff, so for the convenience of the text, made some simple localization processing. If you want to learn directly from the text, you can ignore this article.

If you feel ok, please like more, encourage me to write more wonderful article šŸ™.

Case guide:

Now you have the requirement to convert a multi-parameter function to a combination of n single-parameter functions. If you don’t think of the right way to do it, it’s a coincidence, isn’t it? It’s a coincidence. The purpose of this article is to clarify this question.

Talk is cheap,show you the code


/ / function
add=(first,second,third) = >first+second+third;
// Change the function
add(1) (2) (3) / / 6
add(1.2) (3) / / 6
add(1() () ()2.3) / / 6
Copy the code

First let’s take a look at the definition of Currying (which is accepted by both computer science and mathematics).

Conversion of a multi-parameter function into a single-parameter function is called a Currying function.

An antiderivative function, currified, can take more than one argument at a time. Like the following:

greet = (greeting, first, last) = > `${greeting}.${first} ${last}`;

greet('Hello'.'fei'.'north Chen'); // Hello fan Beichen
Copy the code

After the greet is properly currified

curriedGreet = curry(greet);

curriedGreet('Hello') ('fei') ('north Chen'); // Hello fan Beichen

Copy the code

This ternary function has been transformed into three unary functions. When you provide an argument, a new function is returned expecting the next argument.

Proper cremation

The reason we say proper Curryization above is because some Curryization functions are very flexible when used. The great thing about Corrification is theoretical thinking, but building/calling a function to handle each argument in JS can get tricky.

Ramda’s curriedGreet function allows you to call curriedGreet by:

// Greet requires three parameters: (greeting, first, last)

// All of these will return a function waiting for the remaining arguments (first, last)
curriedGreet('Hello');
curriedGreet('Hello') (); curriedGreet()('Hello') () ();// All of these will return a function waiting for the remaining arguments (last)
curriedGreet('Hello') ('fei');
curriedGreet('Hello'.'fei');
curriedGreet('Hello'() ()'fei') ();// When the number of arguments matches the original definition, the final result will be returned, which will return a string
curriedGreet('Hello') ('fei') ('north Chen');
curriedGreet('Hello'.'fei'.'north Chen');
curriedGreet('Hello'.'fei'() () ()'north Chen');

Copy the code

Notice:

  1. You can choose to pass in more than one argument at a time. This approach is useful in developing applications.
  2. As stated above, you can call a function with an empty parameter and it will return a function that waits for the remaining parameters

Implement a Currified function manually

Mr. Elliot shares a curry implementation similar to Ramda’s.

const curry = (f, arr = []) = >(... args) =>((a) = >(a.length === f.length ? f(... a) : curry(f, a)))([...arr, ...args]);Copy the code

Isn’t that surprising? Whether the heart of ten thousand horses galloping. This thing will do curry.

Solution of plane code

The arrow function of ES6 has been replaced with a more readable way, and debugger has been added to facilitate the analysis of the call process.

curry = (originalFunction, initialParams = []) = > {
  debugger;

  return (. nextParams) = > {
    debugger;

    const curriedFunction = (params) = > {
      debugger;

      if (params.length === originalFunction.length) {
        returnoriginalFunction(... params); }return curry(originalFunction, params);
    };

    return curriedFunction([...initialParams, ...nextParams]);
  };
};
Copy the code

Open the console and let’s take a look at this wonderful code.

The preparatory work

Copy greet and Curry to the console. Type curriedGreet = Curry (greet) and start the journey.

First pause (line 2 of code)

originalFunction
greet
initialParams

Curry (greet) returns a function that waits (N>3). Determine the type of return value on the console, as we analyzed.

And we continue to call the returned function, stored in the sayHello variable.

sayHello = curriedGreet('Hello')
Copy the code

And execute it on the console.

Second pause (line 4 of code)

originalFunction
initialParams
new
closure

Parameter inheritance relationships between parent and child functions

When a parent function is called, they leave their arguments to descendant functions. This inheritance is the same as inheritance in real life.

When defining/initializing, Curry takes originalFunction and initialParams as initial parameters and then returns a child function. So these two variables are not destroyed because the subfunction has access to them as well.

Parse the fourth line

Listening on nextParams, we suddenly see that the value is [‘Hello’]. But we call curriedGreet() with ‘Hello’ instead of [‘Hello’].

Answer: When we call curriedGreet, we get ‘Hello’, but rest syntax converts ‘Hello’ to [‘Hello’].

Why data conversion

Curry is a function that can accept N (N>1,10,100) arguments, so the function can easily access them after processing rest syntax. Since a parameter is passed in each time, rest syntax captures the parameter into an array each time.

Continue to move the breakpoint.

Third pause (line 6 of code)

Line 12 is called before running the debugger on line 6. We define a function called curriedFunction on line 5 and call it on line 12. So we put the breakpoint inside the method body. So when I call curriedFunction, what data is passed in?

[...initialParams, ...nextParams];
Copy the code

In line 5 we look at the arguments… NextParams for ‘Hello’. Therefore, both initialParams and nextParams are arrays, so the spread operator can merge the two arrays.

The key point is right here.

params
originalFunction
greet

JS functions also have the length attribute

This is also a key step in being able to accomplish currization. This is where you determine whether the returned function continues to wait for the remaining arguments. (Just to be clear, this is the end of recursion judgment, if not, will directly lead to an infinite loop)

In JS, the.length attribute of a function is used to indicate that the function has several arguments when it is defined. In other words, the function expects several arguments to participate in the function.

greet.length; / / 3

iTakeOneParam = (a) = > {};
iTakeTwoParams = (a, b) = > {};

iTakeOneParam.length; / / 1
iTakeTwoParams.length; / / 2

Copy the code

If you provide the desired number of arguments, the currization returns to the original function and does nothing else.

However, in the example we provide, the length of the parameters is different from the length of the function. We only provide Hello, so the parameters length is 1, but originalFunction.length is 3. So the if() judgment here is false. We’re going to go the other way. The main function curry is called again, and curry() receives greet and Hello as parameters and restarts the process.

Curry is essentially an infinite loop of self-calling and argument greedy functions until the number of functions === originalfunction.length stops.

Cycle processing

CurriedGreet = curry(greet)

SayHello = curriedGreet(‘Hello’) sayHello = curriedGreet(‘Hello’)

OriginalFunction is greet, but now initialParams is [‘Hello’] instead of an empty array.

Then continue to skip the breakpoint, and double 叒 yi returns a new function (sayHello), and this function also expects the rest of the function to pass in. SayHelloToFan = sayHello(‘ fan ‘).

Continue tracing the breakpoint and skip to the fourth line, where nextParams is [‘ van ‘].

curriedFunction
[' Hello ', 'van']

Why does the array of parameters grow

InitialParams,…nextParams], initialParams is [Hello], nextParams is done by… After the nextParams operation, convert the norm to [‘ norm ‘]. So, at line 12, the two arrays are merged […[‘Hello’],…[‘ van ‘].

Now it is time for the curriedFunction to decide whether params.length is 2 or not.

| | | | | | \ /Copy the code

So we continue with sayHelloToFan. SayHelloToFanBeichen = sayHelloToFan(‘ beichen ‘)

Continue to start the journey of parameter processing and parameter judgment. But now there’s a difference. Originalfunction.length = originalFunction.length = originalFunction.length

| | | | | | \ /Copy the code

There is the same effect and result of calling the greet(‘Hello’,’ fan ‘,’ beichen ‘).

finale

Greet gets the parameters it should, and Curry stops the recursive processing. And we got the result we wanted.

In fact, after processing the greet function with Curry, the function can accept any parameter (nā‰„0) at the same time.

curriedGreet('Hello'.'fei'.'north Chen');
curriedGreet('Hello'.'fei') ('north Chen');
curriedGreet()()('Hello'() ()'fei'() () () () ()'north Chen');

Copy the code

The patch

In the wake of this post, some people have asked whether curry’s benefits can be applied to the real world. This article is simply an introduction to implementing Curry with JS.

But to be clear, Curry is an important concept in functional programming. If you’re actually using this programming method, to be honest, I’m not. However, after reviewing some materials, I plan to try to use them in future projects.

So, to give you a list of the relevant materials I looked up (actually some introduction articles on the official website of functional programming)

  1. Fall in love with Cremation
  2. Why does corrification help
  3. Redux is a common library used in React project development.