David542
David542

Reputation: 110382

Understanding of functional reduction

There are a few examples in this functional programing book about equivalency between different functions. For example, if my understanding is correct it means:

func(param => otherFunc(param))
func(otherFunc)                   // simplified

And, if I can articulate it in words to the best of my ability, I would say:

A function that takes a certain number of parameters and then only returns a second function called with those parameters as arguments is the same as a function that just takes the second function as a single parameter.

Is this a correct understanding? What would be some examples that would show how this would work? The only ones I can think of myself so far are quite trivial, so I'd like to see if I can see more examples to deepen my understanding of some applications of this.

Upvotes: 2

Views: 432

Answers (5)

Eric Breyer
Eric Breyer

Reputation: 720

In the lambda calculus, which is the foundation for a lot of functional programming concepts, this is called Eta reduction and is one of the few fundamental reduction operations in the system.

η-Reduction
LC: λx.f x = f
Javascript: (x => f(x)) = f

It is quite trivial to prove to yourself that this works. "Wrapping" the function in a lambda just explicitly provides the next argument which is applied anyway.

For the sake of brevity assume we have an increment function INC and LC numbers in the church encoding.
LC: (λx.INC x) 3 -> INC 3 -> 4 = INC 3 -> 4
Javascript: (x => INC(x))(3) -> INC(3) -> 4 = INC(3) -> 4

This breaks down a little bit if you have a multi-argument function. Since pure lambda calculus only has unary functions, eta reduction is this simple, but in JS we can use a trick to capture any number of arguments.

JS "Eta" Equvilancy
((...x) => f(...x)) = f

As for applications, you can use this fact to simplify expressions. Other than that there isn't really a reason to expand expressions this way, only reducing them for sake of performance and readability.

Yes, these are equivalent, and this is actually a core functional programming principle that you have discovered.

Upvotes: 2

Claudio
Claudio

Reputation: 3095

I'll try to do my best explanation by step by step examples.

Step 1

Function definition

In javacript you can define a function with 2 different syntax

function inc(number) {
  return number + 1
}

or

const inc = (number) {
  return number + 1
}

// or simplifed

const inc = (number) => number + 1

Those 2 syntax are more or less equivalent but the second one is stays that you have a constant called inc (so a variable) that have a pointer to a piece of memory that contains a function.

So, inc is a variable that contains (as value) the function payload.

Step 2

This step is not related with the previous one

Supposing that we have an array and we want to increment each element into a new array we can use the function map.

Map is a function that creates a new array populated with the results of calling a provided function on every element in the calling array.

Mozilla Reference

const result = [1,2,3,4].map((number) => number + 1)
console.log(result)

Step 3

Let's make ingredients together

We have have a inc function and we want to use that function in our map.

We can do a very simple things.

const inc = (x) => x + 1

const result = [1,2,3,4].map(number => inc (number))
console.log(result)

Step 4

Let's try another approach.

Forget for a moment what we have done on step 3, trying using our inc function. We will try another approac.

At step 1 we have seen that a function is defined as follows.

const inc = (number) => number + 1

The semantics around the equal sign is quite powerful and is saying:

the expression inc

is equivalent to

the expression (number) => number + 1

We can take our inc function definition and the Step 2 code.

const inc = (number) => number + 1
const result = [1,2,3,4].map((number) => number + 1)

On the right side of both lines we have a duplicated expression:

(number) => number + 1

And first line says that an equivalent expression is inc. Trying to apply this approach the results is:

const inc = (x) => x + 1
const result = [1,2,3,4].map(inc)
console.log(result)

when you are trying to read a code written in this way you can imagine to inline inc label with its own expression.

Bonus

Let's apply the approach with more than 1 parameter

Trying to apply this approach for 2 parameter function we need another usecase. Given an array of numbers we want the sum of each number.

To achieve that we'll use the standard reduce function.

Mozilla Reference

const result = [1,2,3,4].reduce((a, x) => a + x, 0)
console.log(result)

now we can extract a sum function and replace the expression with it's label.

const sum = (a, b) => a + b
const result = [1,2,3,4].reduce(sum, 0)
console.log(result)

Upvotes: 2

customcommander
customcommander

Reputation: 18921

It is probably "easier" (YMMV) to see when you evaluate expressions.

Let's start with something simple:

42 === (40 + 1 + 1); // true
42 === (40 + 2);     // true
42 === (42);         // true
42 === 42;           // true

Let's define inc and evaluate it:

const inc = x => x + 1;

inc === (inc); // true
inc === inc;   // true

And so:

inc(41) === (inc)(41); // true
inc(41) === inc(41);   // true

Important bit is here: (inc).

The expression evaluates the function and simply returns it. So when you do (inc)(41) you end up doing inc(41).

Let's inline inc in the right operand:

inc(41) === (x => x + 1)(41); // true
//           ^^^^^^^^^^
//           i.e. inc

Now let's add an unnecessary wrapper:

   inc(41) === (n => (x => x + 1)(n))(41); // true
//              ^     ^           ^   ^^
//              |     |           |   |
//              |     +<<<<<<<<<<<+   |
//              |                 |   |
//              +>>>>>>>>>>>>>>>>>+   |
//              |                     |
//              +<<<<<<<<<<<<<<<<<<<<<+

Let's simplify a little bit:

   inc(41) === (n => inc(n))(41); // true
//              ^        ^   ^^
//              |        |   |
//              |        |   |
//              +>>>>>>>>+   |
//              |            |
//              +<<<<<<<<<<<<+

Or just:

inc(41) === (inc)(41); // true

It also helps to know how to read things:

(n => inc(n))(41)

The expression n => inc(n) (which is a function) is applied to 41. (At this point n is equal to 41.) Then inc is applied to n.

So you might as well just apply inc to 41 directly and skip the first expression, i.e.:

(n => inc(n))(41) === inc(41); // true

Upvotes: 1

Enlico
Enlico

Reputation: 28450

These two

func(param => otherFunc(param))
func(otherFunc)

are not exactly the same, the reason being that these two are not exactly the same

f1 = param => otherFunc(param)
f2 = otherFunc

Indeed look at this example:

// silly function taking two args and producing a string
function f(x,y) {
    return "<" + x + ',' + y + ">";
}

f1 = f;
f2 = x => f(x);

console.log(f1(2,3)); // ok
console.log(f2(2,3)); // not ok!

The reason they are not the same is that x => f(x) is a function that only takes 1 argument, even though the function f expects 2, so when you feed f2 with 2 arguments, it ingnores the second argument, and then calls f with 1 argument only, which results in the second argument (of f) being undefined.

Clearly, you could have written f2 as (x, y) => f(x, y), but then you'd be in trouble if f was ternary.

So, if you don't want to spell out all the arguments of the wrapper function to make it agree with f, you have to write the wrapper function like this

f2correct = (...x) => f(...x);

which expresses the idea that f2correct can take any number of arguments, and it forwards them all to f. In fact, f2correct is equivalent to f.

As regards your understanding of that, I think you got close, but probably misworded it.

A function that takes a certain number of parameters and then only returns a second function called with those parameters as arguments is the same as a function that just takes the second function as a single parameter the second function.

Upvotes: 1

David Raab
David Raab

Reputation: 4488

In JavaScript if you don't use parenthesis, you just get back a function (pointer).

Look at the difference between.

var a = func(x);
var b = func;

The first call executes the function func and returns the result and saves it into a.

The second call (without the parenthesis) returns the function as-is, and just binds it to b. After this, doing a.

var c = b(x)

should be the same as a, as long there are no side-effects.

Now look at the lambda-function.

var d = param => func(param);

Here you just create a function d that calls func and passes it arguments along to func.

You btw. also can write it this way.

var d = function(param) { return func(param) }

and this piece of code can be re-written.

function d(param) {
    return func(param);
}

as you now maybe see. d is the same as func. And again to the loop, this piece of code can be re-written.

var d = func;

Both function are the same.

That's why you can pass otherFunc in your example directly. You just pass the function as-is, without the need to create another new function.

Upvotes: 0

Related Questions