david:
Devil’s advocate: what’s the benefit of a closure in this case over simple recursion? Speed? Both do exactly the same amount of computation. If anything, I would think the closure version would take a little longer because of larger memory footprint being stored with each pass.
Speed is definitely the answer. Memoization is basically trading increased memory use for decrease computation time. If you think the memoized version is performing the same number of calculations, you may have forgotten to consider how the recursion in this is working. If you’re at the point where an extra function call is going to slow you down too much, then this method won’t be very useful.
Here is the call-tree for using an un-memoized fib function. You can see that the call-tree for the arguments 4, 3, 2, 1 & 0 is repeated several times throughout the tree.
[attachment=1]FibRecurse.png[/attachment]
This is the call-tree for the memoized funcion. The segments marked in blue get replaced with a simple look-up instead of a calculation, and the segments marked in pink are no longer called entirely
[attachment=0]FibMemo.png[/attachment]
david:
I thought the main benefit of closures is organizing code. For example, Servoy’s scopes and forms.js are closures by definition. From this viewpoint, not sure how your code example benefits from a closure then.
Our main use-case for rolling our own closures right now is calling api’s that we have created in scopes. Passing in functions for onSuccess and onError instead of iteratively continuing after calling an api seems a lot cleaner and certainly makes the api function itself more “finished” and encapsulated and readable (and all that):
/** @type {_callback} */
var callback = {
onSuccess : function(response) {
application.output(response)
},
onError : function(err) {
application.output("Error " + err.code + ": " + err.msg)
}
}
I think the “main feature” of closures is that you get access to the surrounding external environment of the outer function from within the inner function. Apologies in advance for getting a little pedantic, but there is nothing especially “closurey” in your example, those are more just anonymous functions.
A closure happens (loosely speaking) when you define a function within another function and then return that inner function. Your inner function in that scenario still has access to any variables defined in the outer function (it has closed over them, hence closure). A simple example to demonstrate would be a simple incrementer function. In the example, when you make a call to getIncrementer, x gets set to 0 initially. In outputIncrements, each successive call increments the value of that original x variable by 1.
function getIncrementer()
{
var x = 0;
return function () {
return x++;
}
}
function outputIncrements()
{
var fInc = getIncrementer();
application.output(fInc()); //0
application.output(fInc()); //1
application.output(fInc()); //2
application.output(fInc()); //3
}
There are lots of other things you can do beyond just saving state with them. For instance, if you’re trying to find the hotspots in your code that are taking too long, rather than scattering calls to new Date at the beginning and end of all the functions you want to time, potentially missing some extra returns somewhere and making a general mess of your code, you can use something like the following code instead. This allows you to setup all the timing you want to do in one isolated place (or even do it dynamically in the console), ensure that you’re truly capturing the real start and end times of the call, and clean up is a breeze.
function addTimersToFunctions()
{
timeFunction('scopes.myscope.functionA');
timeFunction('scopes.myscope.functionB');
timeFunction('globals.derp');
timeFunction('forms.user.onAction');
}
function timeFunction(sFunctionPath)
{
if (!sFunctionPath)
{
return false;
}
//Split the function path into its component parts.
var aPath = sFunctionPath.split('.');
var sBaseScope = aPath[0]; //forms|globals|scopes
var sLocalScope = aPath[1]; //formname|scopename
var sFunc = aPath[2] || aPath[1];
//Factory for a timing wrapper.
var fTrace = function (f) {
return function() {
var dStart = new Date();
var oRet = f.apply(this, arguments);
var nTime = (new Date()) - dStart;
application.output(sFunc + " took " + nTime + " milliseconds")
return oRet;
}
}
//Wrap the function in a timer;
switch (sBaseScope){
case 'forms':
forms[sLocalScope][sFunc] = fTrace(forms[sLocalScope][sFunc]);
break;
case 'globals':
globals[sFunc] = fTrace(globals[sFunc]);
break;
case 'scopes':
scopes[sLocalScope][sFunc] = fTrace(scopes[sLocalScope][sFunc]);
break;
default:
return false;
}
return true;
}