Reputation: 110382
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
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
Reputation: 3095
I'll try to do my best explanation by step by step examples.
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.
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.
const result = [1,2,3,4].map((number) => number + 1)
console.log(result)
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)
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.
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.
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
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
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 parameterthe second function.
Upvotes: 1
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