最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Where did the argument go in this example? - Stack Overflow

programmeradmin1浏览0评论

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?

Share Improve this question edited Jul 17, 2016 at 21:04 jonrsharpe 122k30 gold badges267 silver badges474 bronze badges asked Jul 17, 2016 at 21:01 SvenSven 13.3k29 gold badges97 silver badges156 bronze badges 4
  • 3 ajaxCall takes a single callable argument that itself takes a single argument, the json. 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
Add a ment  | 

5 Answers 5

Reset to default 5

The 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.

发布评论

评论列表(0)

  1. 暂无评论