I think it's implemented like this:
function renderPage($stories) {
renderHeaders();
renderStories($stories);
renderMoreLink(storeLink(function() {
renderPage(next30($stories));
}));
}
function handleLinkClicked($id) {
$code = fetchLink($id);
$code();
}
Using storeLink() you save a function, than can be later executed when user clicked a particular link (remember, HN runs as a single process and does not restart itself every time like say, PHP does). This function remembers the context in which it was created - in this case, $stories variable - so all the data required to fulfil the request at later time gets stored with the function.https://github.com/wting/hackernews/blob/master/news.arc#L92...
The point is that all the variable you want for rendering "page 2" are right there in scope.
A closure can capture those, so you don't have to do any extra work serialising them, sending them to the browser, validating them on return, etc.
Closures can seem odd the first time you come across them, but they're pretty mainstream these days (perl, python, ruby, C++, C#, golang all have them (some of them for a very long time :-), as well as the more traditionally-functional languages).
There's more info here and it's the sort of topic you'll get lots of answers from in HN. http://en.wikipedia.org/wiki/Closure_%28computer_programming...
At one end of the spectrum of asynchronous design, the behavior of "More" is determined after the user has clicked on it. At the other other end of the spectrum, the "More" link's behavior is determined at the time the page is constructed. HN uses the latter approach.
Assuming a click on "More", with early binding, the application only reads it's state once (at the time of page construction). With late binding it reads it's state twice, once for page construction and again to construct the next page.
With early binding, only the name of the function has to be passed back immediately, construction of the function itself can be queued depending on server load (e.g. the function can be built 100ms later) and the user is likely unaffected. With late binding a 100ms delay in page building due to server load happens while the user is waiting.
The cost of the continuation approach is that a lot of continuations may be constructed that never get run. That's often less of a worry for people with garbage collectors.
function adder(x) {
return function(y) {
return x+y;
};
}
var plusthree = adder(3);
var plusnine = adder(9);
plusthree(2); // 5
plusnine(3) // 12
plusthree(4); // 7
adder is a function that returns a closure that closes over x. If the function that's returned were a normal function rather than a closure, the x that you pass to adder would not within the scope of the inner function, and you wouldn't be able to reference it. Calling plusthree or plusnine would simply fail. However, because the inner function is a closure, it remembers x.Every time you call adder, a new closure is created, with its own version of x, so you don't have to do anything extra like creating an object to store it in. The language runtime takes care of all that boring stuff for you.