Eric Elliott-Curry and Function Composition

Curry and the function

Caution: This article is slightly longer, so read it carefully

A few days ago, I read an article that said that the Currization function is a higher-order function, but the name sounds a little bit higher. Today, I went to Medium and found this one again, which felt good. It involves closures and point-free style, and it is not a full-head Amway demo.

What is thecurriedfunction

The Curried function is a function that takes multiple arguments one at a time. To be clear, for example, given a function with three arguments, curried’s version of the function takes one argument and returns a function that takes the next argument, which returns a function that takes the third argument. The last function returns the result of applying the function to all of its arguments.

Look at the following example, for example, given two numbers, a and b of the form curried, return the sum of a and b:

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

Then you can call it directly:

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

First, the function takes a as an argument, then returns a new function, then passes B to the new function, and finally returns the sum of a and b. Only one argument is passed at a time. If the function has more arguments than just a and b above, it can continue to return new functions as above, until it finally terminates the function.

The add function takes an argument and then returns a fixed set of functions within the closure’s scope. Closures are functions that are bundled with lexical scopes. Closures are created during function creation, which you can learn more about here. Fixed means that variables are assigned within the bound scope of the closure.

Look at the code below: Add is called with argument 2, returns a partially applied function, and fixes a to 2. Instead of assigning the return value to the variable or otherwise using it, we call the returned function immediately by passing 3 to it in parentheses, completing the entire function and returning 5.

What are partial functions

A partial application is a function that has been applied to some but not all parameters. In plain English, a function with fixed (unchanging) parameters in the closure scope. Features with some parameters fixed are considered partially applied.

What’s the difference?

Partial Applications can use as many or as many parameters at a time as needed. A curried function returns one unary function at a time: a function that takes one argument at a time.

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

This requirement for unary functions is an important feature for Curried.

What is thepoint-freestyle

Point-free is a programming style in which function definitions do not reference function parameters.

Let’s look at the definition of a function in js:

function foo (/* parameters are declared here*/) {
  // ...
}
const foo = (/* parameters are declared here */) = > // ...
const foo = function (/* parameters are declared here */) {
  // ...
}
Copy the code

How do I define functions in JavaScript without referring to required parameters? Well, we can’t use the function keyword, and we can’t use arrow functions (=>) because they need to declare formal arguments (referencing its arguments). So all we need to do is call a function that returns a function.

Create a function that increments any numbers passed to it using point-free. Remember, we already have a function called Add that takes a number and returns a partial Application function whose first argument is fixed to whatever you passed in. We can use it to create a new function called inc() :

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

This becomes interesting as a mechanism for generalization and specialization. The function returned is just a specialized version of the more general add() function. We can create any number of dedicated versions using add() :

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

Of course, these all have their own closure scope (closures are created when the function is created – when add() is called), so the original inc() keeps working:

inc(3) / / 4
Copy the code

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

Then when we call inc(3), the b argument in add() is replaced with the parameter value 3, and the application completes, returning the sum of 1 and 3.

All curried functions are a form of higher-order functions that allow you to create specialized versions of the original function for the specific use case at hand.

Why do you want tocurry

The Curried function is particularly useful in the context of function composition.

In algebra, two functions f and g are given:

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

We can combine these functions to create a new function (h) from a directly to C:

// Algebraic definition, using the '. 'combined operator
/ / from Haskell
h: a -> c
h = f . g = f(g(x))
Copy the code

In js:

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

Definition of algebra:

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

Can be translated into JavaScript:

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

But that only makes up two functions at a time. In algebra, you can write:

g . f . h
Copy the code

We can write a function to write any number of functions. In other words, compose() creates a pipeline of functions where the output of one function is connected to the input of the next. Here’s how I always write:

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

This version takes any number of functions and returns a function that takes the initial value x, then uses reduceRight() to iterate over each function F from right to left in FNS, and then applies it in turn to the cumulative value Y. The function we accumulate in the accumulator, where y is the return value of the function returned by compose().

Now we can write our composition like this:

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

Code tracing (trace)

Using point-free style function combinations creates very clean, readable code, but it is not easy to debug. What if I want to check the values between functions? Trace () is a handy function that lets you do just that. It takes the form of the curried function:

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

Now we can use this to check the function:

const compose = (. fns) = > x => fns.reduceRight((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;
/*
Note: function application order is
bottom-to-top:
*/
const h = compose(
  trace('after f'),
  f,
  trace('after g'),
  g
);
h(20);
/*
after g: 21
after f: 42
*/
Copy the code

Compose () is a great utility, but when we need to write more than two functions, it can sometimes come in handy if we can read them from top to bottom. We can do this by reversing the order in which the functions are called. There is another composite utility called PIPE (), which is composed in reverse order:

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

Now we can rewrite the above with pipe:

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;
/*
Now the function application order
runs top-to-bottom:
*/
const h = pipe(
  g,
  trace('after g'),
  f,
  trace('after f')); h(20);
/*
after g: 21
after f: 42
*/
Copy the code

Curry and functionality together

Even outside of the context of function composition, Curry is certainly a useful abstraction to do specific things. For example, a curried version of Map () can be dedicated to many different things:

const map = fn= > mappable => mappable.map(fn);
const pipe = (. fns) = > x => fns.reduce((y, f) = > f(y), x);
const log = (. 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 Curried functions is that they simplify function composition. Functions can take any number of inputs, but can only return a single output. For the function to be composable, the output type must be aligned with the expected input type:

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

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

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

How do we get x in this situation? Usually, the answer is Curry G.

Remember that the definition of a curried function is a function that takes one argument at a time by taking the first argument and returning a series of functions, each taking the next argument until all the arguments are collected.

The key word in this definition is “one at a time.” What makes curry functions so convenient for function composition is that they transform functions that expect multiple arguments into functions that can take a single argument, allowing them to fit into the function composition pipeline. Take the trace() function as an example, starting from the front:

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 accepts only one argument at a time, allowing us to specialize inline functions. If Trace weren’t Curry, we wouldn’t be able to use it in this way. We must write the pipe like this:

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' calls are no longer 'point-free',
  // Introduce an intermediate variable, 'x'.
  x => trace('after g', x),
  f,
  x => trace('after f', x),
);
h(20);
Copy the code

But a simple Curry function is not enough; you also need to make sure that the function specializes them in the correct order of arguments. See what happens if we curry trace() again, but reverse the argument order:

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

If you don’t want to do this, you can solve the problem with a function called flip(), which simply flips the order of the two arguments:

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

Now we can create a flippedTrace function:

const flippedTrace = flip(trace);
Copy the code

Use this flippedTrace again 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

You can see that this also works, but you should write the function the right way in the first place. This style is sometimes called “data last,” which means you should get the special arguments first and get the data that the function worked on last.

Look at the function in its original form:

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

Each application of Trace creates a dedicated version of the trace function used in the pipeline, where the label is pinned to the returned trace part of the application. So this:

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

Equivalent to the following:

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

If we exchange trace(‘after g’) for traceAfterG, it means the same thing:

const pipe = (. fns) = > x => fns.reduce((y, f) = > f(y), x);
const trace = label= > value => {
  console.log(`${ label }: ${ value }`);
  return value;
};
// The curried version of trace()
// saves us from writing all 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

The curried function is a function that takes multiple arguments, one at a time, by taking the first argument, and returns a series of functions, each taking the next argument, until all arguments have been fixed and the function application can finish, at which point the resulting value is returned.

A partial application is a function that has been applied to some – but not all – parameters. The arguments to which the function has been applied are called fixed arguments.

Point-free style is a way to define a function without referring to its arguments. Typically, a point-free function is created by calling a function that returns a function (such as curried).

Curried functions are great for function composition because they allow you to easily convert n-meta functions into the unary function form required by the function composition pipeline: the function in the pipeline must take only one argument.

The final functions of the data are easy to combine because they can be easily used with point-free style.