I am currently reading the Mostly Adequate Guide on functional programming, chapter 2.
There, the following example is given
var getServerStuff = function(callback) {
return ajaxCall(function(json) {
return callback(json);
});
};
which is then refactored into:
var getServerStuff = ajaxCall;
While explaining the refactoring, the author argues that
return ajaxCall(function(json) {
return callback(json);
});
is the same as
return ajaxCall(callback);
While I understand that ajaxCall
is called with the return value of the anonymous function (which is just the return value of callback
), I don't get how the refactored version is supposed to work - where did the json
argument go? What am I missing?
I am currently reading the Mostly Adequate Guide on functional programming, chapter 2.
There, the following example is given
var getServerStuff = function(callback) {
return ajaxCall(function(json) {
return callback(json);
});
};
which is then refactored into:
var getServerStuff = ajaxCall;
While explaining the refactoring, the author argues that
return ajaxCall(function(json) {
return callback(json);
});
is the same as
return ajaxCall(callback);
While I understand that ajaxCall
is called with the return value of the anonymous function (which is just the return value of callback
), I don't get how the refactored version is supposed to work - where did the json
argument go? What am I missing?
-
3
ajaxCall
takes a single callable argument that itself takes a single argument, thejson
. If all that callback does is call another function with the same arguments, it can be replaced by that function. – jonrsharpe Commented Jul 17, 2016 at 21:03 - It is similar, though not exactly equivalent, to what is called inlining in more imperative languages. – Frédéric Hamidi Commented Jul 17, 2016 at 21:04
- @jonrsharpe why is that ? why can it be replaced ? Is there a name for this ? – Yasin Yaqoobi Commented Jul 17, 2016 at 21:20
- @YasinYaqoobi I don't know what you mean by "why" - it can be replaced because it's (almost) exactly equivalent. – jonrsharpe Commented Jul 18, 2016 at 7:35
5 Answers
Reset to default 5The question has been answered but I think some bolding and strikethroughs make it very easy to see the code conversions. Hopefully this answer helps anyone that's struggling to visualize the problem.
You wouldn't write …
var floor = function(x) { return Math.floor(x) }
Instead, you would write …
var floor = Math.floor
… and it will work exactly the same†. This is called an Eta Conversion and if you repeat it twice, you will see how Brian got his result in the Mostly Adequate Guide.
The basic rule of Eta conversion is this:
function(x) { return f(x) } === f
… they're pletely interchangeable†
You can use the same technique in the original code
var getServerStuff = function(callback) {
return ajaxCall(function(json) {
return callback(json)
})
}
First look at …
return ajaxCall(function(json) { return callback(json) })
Eta conversion says …
function(json) { return callback(json) } === callback
So let's look at the whole code with the results of the first eta conversion …
// first step
var getServerStuff = function(callback) {
return ajaxCall(function(json) {
return callback(json)
})
}
// eta converts to ...
var getServerStuff = function(callback) {
return ajaxCall(callback)
}
This scenario should feel familiar to us. One more eta conversion will bring us to the final simplified form. I'll add bolding one more time so we can see it better
Eta conversion says …
function(callback) { return ajaxCall(callback) } === ajaxCall
// second step
var getServerStuff = function(callback) {
return ajaxCall(callback)
}
// eta converts to ...
var getServerStuff = ajaxCall
†for all intents and purposes, they are interchangeable. Mostly Adequate Guide to FP shows little concern for dynamic binding or use of this
It's a functional refactoring and it's much clearer doing it the other way around. Imagine you have a function, ajaxCall
that takes a callback function which it will apply with the result eg.
ajaxCall(fun);
If you were to replace fun
with function(json){ return fun(json); }
by the rules of substitution it's the exact same code. Thus you could write:
ajaxCall(function(json){ return fun(json);});
Just like we wrapped fun
we can wrap ajaxCall
, we introduce callback
as a substitution for fun
and give fun
as argument. By the rules of substitution its the exact same code:
(function(callback){
return ajaxCall(function(json){ callback(json);});
})(fun)
Now this isn't the same as you got since I actually call it in my example, but you may see a resemblance with your code when we split up definition and call:
var getServerStuff = function(callback) {
return ajaxCall(function(json){return callback(json);});
};
getServerStuff(fun);
If you could follow that chain your text does it in the opposite way by replacing the function wrapper in the argument to ajaxCall:
var getServerStuff = function(callback) {
// return ajaxCall(function(json){return callback(json);});
return ajaxCall(callback);
};
getServerStuff(fun);
And then seeing the getServerStuff actually also is just a wrapper with no added features:
// var getServerStuff = function(callback){ return ajaxCall(callback);}
var getServerStuff = ajaxCall;
getServerStuff(fun);
Then getServerStuff is just an alias so we can substitute it with the value:
ajaxCall(fun);
There you go. Right where you started. For this to work however the variables you introduce cannot shadow variables used in the affected code and the substituted functions must support the same arity as the original. Other than that it's pretty much straight forward.
Jim Weirich shows how to do the factorial function using only anonymous functions by doing refactorings by the standard refactoring rules. It's in Ruby but I didn't have a problem following it. At 21:15 he shows functional refactoring which is the important on topic part.
While I understand that ajaxCall is called with the return value of the anonymous function (which is just the return value of callback)
This is actually backwards.
ajaxCall
is called with an anonymous function as an argument. It presumably eventually calls that function, and that function then calls callback
.
Giving all the functions names generally makes this easier to understand.
function callback(json) {
console.log('I was called with ' + json);
}
function intermediate(json) {
return callback(json);
}
ajaxCall(intermediate);
Here, ajaxCall
gets passed intermediate
. When called, intermediate
takes its single argument and passes it along to callback
.
This chain is a little unnecessary and can be simplified to this:
function callback(json) {
console.log('I was called with ' + json);
}
ajaxCall(callback);
I hope that helps!
getServerStuff
is a higher order function that expects a lambda (anonymous function) as its argument (which itself expects one argument).
ajaxCall
is a higher order function that also expects a lambda (which itself expects one argument).
Where are the data (JSON)? Let's eliminate the distracting details:
const hof = cb => hof2(x => cb(x)); // higher order function that expects a lambda
const hof2 = cb => cb(); // another HOF that expects a lambda
const inc = x => x + 1;
hof(inc); // NaN
This is pointless. Again, where are the data? They are fetched asynchronously from a server. So we need to adapt our functions a bit:
const inc = x => console.log(x + 1);
const hof = cb => hof2(x => cb(x));
const hof2 = cb => setTimeout(cb, 0, 2); // assumed `2` is the result of the async operation
hof(inc); // 3
At the end we have a HOF that calls another HOF that fetches data from a server and applies its lambda (callback) to that data.
We can obviously further simplify that. We just need a HOF that expects a lambda that expects data, which is provided asynchronously by a server:
const inc = x => console.log(x + 1);
const hof2 = cb => setTimeout(cb, 0, 2); // assumed `2` is the result of the async operation
hof2(inc); // 3
The last line is equivalent to ajaxCall(callback)
;
Conclusion: The example is hard to follow, because the data (json) is passed as the result of an asynchronous function invocation and not by the initially calling code.
Supplement: If all functions in your code are unary that is, they expect exactly one argument, then there is no more arity - it is abstracted. This leads to function position and pointfree style, two topics that are described in the Mostly Adequate Guide as well.
The point is that
function (json) {
return callback(json)
}
is equivalent to just
callback
There is no function expression any more, so there is no need to have a parameter json
. Instead of calling the anonymous function (that in turn calls callback
with the argument and passes its result back), you can simply call callback
directly to the same effect.