• Curry and Function Composition
  • By Eric Elliott
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Zi Fei
  • Proofreader: Wuzhengyan2015, TTtuntuntutu

Smoke Art from Cube to Smoke — MattysFlicks — (CC BY 2.0)

Note: This article is part of the “Composite Software” series, which aims to learn functional programming and composite software techniques from scratch in a JavaScript ES6+ environment. Stay tuned. We’ll talk a lot about that! On a | < < < the first paper

With the dramatic rise of functional programming in mainstream JavaScript, currie functions have become common in many applications. It’s important to understand what they are, how they work, and how to use them effectively.

What is a Currie function?

A Currization function is a function that takes more than one parameter to take only one parameter at a time. If a function takes three arguments, the Currified function takes one argument and returns a function to accept the next argument, which passes in the third argument. The last function returns the result of the function with all parameters applied.

You can do the same thing with more or fewer parameters. For example, if you have two numbers, the Corrified form of a and B will return the sum of a and b.

// add = a => b => Number
const add = a => b => a + b;
Copy the code

To use it, we must apply the function application syntax to both functions. In JavaScript, the parentheses () after a function trigger the function call. When a function returns another function, the returned function can be called immediately with an extra pair of parentheses:

const result = add(2)(3); / / = > 5Copy the code

First, the function takes argument A and returns a new function, which takes b and returns the sum of a and b. Take one parameter at a time. If the function has more arguments, it simply keeps returning the new function until all the arguments are provided, at which point the application is complete.

The add function takes an argument and returns its own partial application, with a pinned in the closure scope of the partial application. Closures are functions bound to their syntactic scope. Closures are created when the create function runs. Fixed means that variables are assigned within the scope of the closure binding.

The parentheses in the above example represent a function call: add is called with 2 as an argument to return the partial function application and a is fixed at 2. Instead of assigning the return value to the variable or otherwise using it, we call the return function immediately by passing 3 to it in parentheses, thus completing the application and returning 5.

What are Partial Applications?

Partial function application refers to taking a function and applying it to one or more arguments, but not all of them. In other words, it is a function that already has some fixed parameters in the closure scope. Partial function applications are functions that have some fixed parameters.

What’s the difference between them?

Partial function applications can take as many or as few arguments at a time as needed. The Currization function always returns a unary function: the function always takes one argument.

All Currization functions return partial applications, but not all partial applications are the result of Currization.

The unary requirement of currization functions is an important feature.

What is point-free style?

Point-free style is a programming style in which function definitions do not correlate function parameters. Let’s look at a function definition in JavaScript:

functionFoo (/* here we define arguments */) {//... } const foo = (/* here we define arguments */) => //... const foo =function(/* here to define arguments */) {//... }Copy the code

How can you define functions in JavaScript that have no associated parameters? We cannot use the function keyword, nor the arrow function (=>), because these require formal parameter declarations. So what we do is we call a function that returns a function.

Use the no-dot style to create a function that increments any number you pass in. Remember, we already have a function called add that takes a number as an argument and returns a partial function with a fixed first argument whatever value you pass in. We can use this method to create a new function called inc().

// inc = n => Number // Add one to any Number. const inc = add(1); inc(3); / / = > 4Copy the code

As a generalization and proprietary mechanism, this is interesting. The returned function is simply a specialized version of the more general add() function. We can use add() to create as many specialized versions as needed.

const inc10 = add(10); const inc20 = add(20); inc10(3); // => 13 inc20(3); / / = > 23Copy the code

Of course, all of these have their own closure scope (closures are created when the function is created — when add() is called), so it turns out that inc() can keep functionality:

inc(3) // 4
Copy the code

When we call add(1) to create inc(), the a argument in add() is fixed to 1 in the returned function, which is assigned to inc.

When we call inc(3), the b argument in add() is replaced by argument 3, and the function ends, returning the sum of 1 and 3.

All Currization functions are higher-order functions, which allow you to create specialized versions of the original function for specialized purposes.

Why do I want to curryize the function?

Currization functions are extremely useful in function combinations.

In algebra, suppose there are two functions, f and g:

f: a -> b
g: b -> c
Copy the code

You can combine these two functions to create a new function H, which gets c directly from A:

H: a -> c h = f. g = f(g(x))Copy the code

In JavaScript:

const g = n => n + 1; const f = n => n * 2; const h = x => f(g(x)); h(20); / / = > 42Copy the code

Algebraic definition:

f . g = f(g(x))
Copy the code

Can be converted to JavaScript:

const compose = (f, g) => f(g(x));
Copy the code

But you can only combine two functions at a time. In algebra, it is possible to write:

g . f . h
Copy the code

We can combine as many functions as we want into one function. In other words, compose() creates a pipe in a function that connects the output of one function to the input of the next.

I often write this way:

const compose = (... fns) => x => fns.reduceRight((y, f) => f(y), x);Copy the code

This version takes any number of functions and returns one function that requires an initial value, then uses reduceRight() to walk through each function from right to left, the f in FNS, and turn it into the cumulative value Y. The calculated value y of the accumulator in the function compose() is the return value of the accumulator.

Now we can combine them like this:

const g = n => n + 1; const f = n => n * 2; / / use ` compose (f, g) ` replace ` x = > f (x) (g) ` ` const h = compose (f, g); h(20); / / = > 42Copy the code

Trace

The combination of functions uses a point-free style to create very clean and understandable code, but it takes a bit of work to debug easily. If you want to check the values between functions? One handy tool you can use is trace(). It requires the form of the Currization function:

const trace = label => value => {
  console.log(`${ label }: ${ value }`);
  return value;
};
Copy the code

Now let’s check the pipes:

const compose = (... fns) => x => fns.reduceRight((y, f) => f(y), x); const trace = label => value => { console.log(`${ label }: ${ value }`);
  returnvalue; }; const g = n => n + 1; const f = n => n * 2; /* const h = compose(trace() const h = compose()'after f'),
  f,
  trace('after g'),
  g
);

h(20);
/*
after g: 21
after f: 42
*/
Copy the code

Compose () is a useful tool, but the top-down order is easier to read when you need to compose more than two functions. We can do this by reversing the order of the functions being called. Here’s another composition tool called PIPE that reverses the order of composition:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x);Copy the code

Now we can write the following code:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = label => value => { console.log(`${ label }: ${ value }`);
  returnvalue; }; const g = n => n + 1; const f = n => n * 2; /* const h = pipe(g, trace(); /* const h = pipe(g, trace();'after g'),
  f,
  trace('after f')); h(20); /* after g: 21 after f: 42 */Copy the code

Combination of Currie and function combinations

Even outside of the realm of function combinations, Currization is a very useful abstraction that we can apply to specialized functions. For example, a Currified version of a map can be specialized to do many different things:

const map = fn => mappable => mappable.map(fn); const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); constlog= (... args) => console.log(... args); const arr = [1, 2, 3, 4]; const isEven = n => n % 2 === 0; const stripe = n => isEven(n) ?'dark' : 'light';
const stripeAll = map(stripe);
const striped = stripeAll(arr);
log(striped); / / = > ["light"."dark"."light"."dark"]

const double = n => n * 2;
const doubleAll = map(double);
const doubled = doubleAll(arr);
log(doubled);
// => [2, 4, 6, 8]
Copy the code

But the real power of Currizing functions is that they simplify function combinations. A function can take any number of inputs and return only one output. For the function to be composable, the output type must be the same as the expected input type:

f: a => b
g:      b => c
h: a    =>   c
Copy the code

If the above function g expects two arguments, the output of f will be inconsistent with the input of g:

f: a => b
g:     (x, b) => c
h: a    =>   c
Copy the code

How do you pass x into g in this case? The answer is to currize g.

Remember the definition of a Coriolization function: a function that takes multiple arguments into a function that takes only one argument at a time, and returns a series of functions by taking the first argument until all the arguments have been collected.

The keyword defined above is “passing in one parameter at a time.” What makes Curryization functions so convenient for function composition is that they transform functions that require multiple arguments into functions that require only one argument, allowing them to fit into the function composition pipeline. Take the previous trace() function for example:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = label => value => { console.log(`${ label }: ${ value }`);
  return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
  g,
  trace('after g'),
  f,
  trace('after f')); h(20); /* after g: 21 after f: 42 */Copy the code

Trace () defines two arguments, but takes only one argument at a time, allowing us to specialize inline functions. If trace() is not currified, it cannot be used in this way. We must write the pipe function like this:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = (label, value) => { console.log(`${ label }: ${ value }`);
  returnvalue; }; const g = n => n + 1; const f = n => n * 2; Const h = pipe(g, // trace() is no longer dotless and introduces' x 'as an intermediate variable. x => trace('after g', x),
  f,
  x => trace('after f', x),
);

h(20);
Copy the code

But it’s still not enough to have a purely Currified function. You also need to make sure that the parameters expected by the function are specialized in the correct order. Look again at what happens when we currie trace(), but this time we reverse the order of the arguments:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = value => label => { console.log(`${ label }: ${ value }`);
  returnvalue; }; const g = n => n + 1; const f = n => n * 2; Const h = pipe(g, // trace() cannot be point-free because the desired parameters are in the wrong order x => trace(x)('after g'),
  f,
  x => trace(x)('after f')); h(20);Copy the code

If necessary, you can solve this problem using the Flip method, which simply reverses the order of the two arguments:

const flip = fn => a => b => fn(b)(a);
Copy the code

Now we can create the flippedTrace() function:

const flippedTrace = flip(trace);
Copy the code

And use it like this:

const flip = fn => a => b => fn(b)(a); const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = value => label => { console.log(`${ label }: ${ value }`);
  return value;
};
const flippedTrace = flip(trace);

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
  g,
  flippedTrace('after g'),
  f,
  flippedTrace('after f')); h(20);Copy the code

But it’s better to write the right function in the first place. This style is sometimes referred to as “data behind,” which means you need to pass in the specializations first and execute the function at the end. Here is the original form of the function:

const trace = label => value => {
  console.log(`${ label }: ${ value }`);
  return value;
};
Copy the code

Each time trace() applies label, it creates a dedicated version of the trace function that is used in the pipe, where label is fixed in the application of the partial function returned by trace. So:

const trace = label => value => {
  console.log(`${ label }: ${ value }`);
  return value;
};

const traceAfterG = trace('after g');
Copy the code

. Is equal to:

const traceAfterG = value => {
  const label = 'after g';
  console.log(`${ label }: ${ value }`);
  return value;
};
Copy the code

If we replace trace(‘after g’) with traceAfterG, it equals the following:

const pipe = (... fns) => x => fns.reduce((y, f) => f(y), x); const trace = label => value => { console.log(`${ label }: ${ value }`);
  returnvalue; }; // The currified version of trace() allows us to avoid this code... const traceAfterG = value => { const label ='after g';
  console.log(`${ label }: ${ value }`);
  return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
  g,
  traceAfterG,
  f,
  trace('after f')); h(20);Copy the code

conclusion

A Cremation function is one that converts a function that takes multiple arguments into a function that takes a single argument by taking the first argument and returning a series of functions that take the remaining arguments until all arguments have been used and the function is finished, at which point the result is returned.

A partial function application is a function that has applied some but not all parameters. The Parameters to which a function has been applied are called Fixed Parameters.

A point-free style is a style of function definition that does not require references to parameters. In general, pointless functions are created by calling functions that return functions, such as coriolization functions.

Coriolization functions are useful for function combinations because you can easily convert n-yuan functions to unary form for function combinations: the function in the pipe must have a single parameter.

Post-data functions are handy for function composition because they can be easily used in a point-free style.

The next step

EricElliottJS.com members can see a full tutorial video on this topic. Members can access the ES6 Curry & Composition course.


Eric Elliott isProgramming JavaScript Applications (O ‘reilly)And is a software tutorial platformDevAnywhere.ioCofounder of. He has experience working for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC and top music artists including Usher, Frank Ocean, And Metallica.

He has the most beautiful woman in the world with him working remotely all over the world.

Thank JS_Cheerleader.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.