JSDoc for functions

I’m having a few problems adding the correct JSDoc annotations to functions.

In the following code, I’ve attempted to annotate fib, fib2 and localFib in various ways using examples from Servoy’s documentation, as well as documentation for JSDoc Toolkit and the Closure compiler.
Unfortunately everything I have tried results in the warning “The function () is not applicable for the arguments (Number)” showing up on my calls to each of the functions.
Am I doing something incorrectly, or is there no way to annotate what I’m attempting?

/**
 * Provide a memoized version of a function
 * @param {Function} f
 * @return {Function}
 * @properties={typeid:24,uuid:"2C6DF5DF-7022-4774-B707-05304853334F"}
 */
function memo(f) {
	var oCache = {};
	return function () {
		var sKey = JSON.stringify(arguments);
		if (!(sKey in oCache))	{
			oCache[sKey] = f.apply(this, arguments);
		}
		return oCache[sKey];
	}
}

/**
 * @param {Number} n
 * @return {Number}
 * @properties={typeid:35,uuid:"07B79A37-F9B1-457D-B491-CD8E4D3AD534",variableType:-4}
 */
var fib = memo(function fib(n) {
	switch (n) {
		case 0:
			return 0;
		case 1:
		case 2:
			return 1;
		default:
			return fib(n-1) + fib(n-2)
	}
})

/**
 * @type {function(Number):Number}
 * @properties={typeid:35,uuid:"1A48323C-F457-460D-8926-CFD92B271CA0",variableType:-4}
 */
var fib2 = memo(function fib2(n) {
	switch (n) {
		case 0:
			return 0;
		case 1:
		case 2:
			return 1;
		default:
			return fib2(n-1) + fib2(n-2)
	}
})


/**
 * @properties={typeid:24,uuid:"02A44245-58F7-44F4-AB47-474661E6F609"}
 */
function testFib() {
	var result1 = fib(10);
	var result2 = fib2(10);
	
	/** @type {function(Number):Number} */
	var localFib = memo(function (n) {
		switch (n) {
			case 0:
				return 0;
			case 1:
			case 2:
				return 1;
			default:
				return localFib(n-1) + localFib(n-2)
		}
	})
	localFib(5);
}

Cool code to pick through. Lot’s of interesting stuff going on (object string as an object parameter, recursion, saved functions, apply(), fib logic).

I fiddled with jsdocs a bunch as well to see if I could type an anonymous function with parameters – no success.

Obviously this will get rid of the warnings but it’s stupid:

return eval("localFib(n-1)") + eval("localFib(n-2)")

The other option is to pull apart the code a bit so Servoy can deal with a named function instead:

/**
* Provide a memoized version of a function
* @param {Function} f
* @return {Function}
* @properties={typeid:24,uuid:"2C6DF5DF-7022-4774-B707-05304853334F"}
*/
function memo(f) {
   var oCache = {};
   return function () {
      var sKey = JSON.stringify(arguments);
      if (!(sKey in oCache))   {
         oCache[sKey] = f.apply(this, arguments);
      }
      return oCache[sKey];
   }
}

/**
 * @param {Number} n
 *
 * @properties={typeid:24,uuid:"971B17FD-0852-4F4D-BFA5-79FE114C863A"}
 */
function engine(n) {
	switch (n) {
	case 0:
		return 0;
	case 1:
	case 2:
		return 1;
	default:
		return engine(n-1) + engine(n-2)
	}
}


/**
* @properties={typeid:24,uuid:"02A44245-58F7-44F4-AB47-474661E6F609"}
*/
function testFib() {
	application.output(engine(10))
}

Nice brain teaser to go with my morning coffee! Took me an hour of staring to figure out what was going on though. There’s an emoticon for that… :oops:

Dave:
Thanks for taking a stab at it :)
However, your implementation loses the benefits of wrapping the fib function in the memoizer. ```
var fib = memo(function fib(n) {…})


I think for this to work I need type declarations for functions to override what Servoy thinks it knows about the function, much like you can with other things, e.g. JSRecord objects like the following code where getUserRecord overrides the returntype of recordFromID to be a specific type of record, instead of a generic one:

/**

  • Get a record by id
  • @param {String} dataSource
  • @param id
  • @return {JSRecord}
    */
    function recordFromID(dataSource, id)
    {
    var fs = databaseManager.getFoundSet(dataSource);
    var dataSet = databaseManager.convertToDataSet(
    [id],
    databaseManager.getTable(fs.getDataSource()).getRowIdentifierColumnNames());
    fs.loadRecords(dataSet);
    return fs.getRecord(1);
    }

/**

  • @param id
  • @return {JSRecorddb:/demo/user>}
    /
    function getUserRecord(id)
    {
    /
    * @type {JSRecorddb:/demo/user} */
    var user = recordFromID(“db:/demo/user”,id);
    return user;
    }

I think you can resolve the warnings quite easily by typing the returned function inside the memo function, like:

function memo(f) {
   var oCache = {};
    /**
	 * @param {Number} n
	 * @return {Number}
	 */
	var fu = function (n) {
      var sKey = JSON.stringify(arguments);
      if (!(sKey in oCache))   {
         oCache[sKey] = f.apply(this, arguments);
      }
      return oCache[sKey];
   }
   return fu
}

I haven’t spent a lot of time to analyse the code to see if I’m now typing the function correctly, but AFAICS the returned function in memo takes 1 Number and returns one Number

Paul

Paul–

That would only work if memo was only used on fib, or other functions with similar method signatures. However, memo is a generally applicable function…it can be used to provide a “memoized” version of any function. Basically, the only thing you can know about the return type of memo is that it will be a function. The only place that can know the specific return type of memo is the caller, hence the need to be able to override the full type declaration of functions.

ok, didn’t look at it that long to grasp that.

So, what about this then:

/**
 * Provide a memoized version of a function
 * @param {Function} f
 * @return {Function}
 * @properties={typeid:24,uuid:"2C6DF5DF-7022-4774-B707-05304853334F"}
 */
function memo(f) {
	var oCache = { };
	/**
	 * @param {...*} arg
	 * @return {Function}
	 */
	var fu = function(arg) {
		var sKey = JSON.stringify(arguments);
		if (! (sKey in oCache)) {
			oCache[sKey] = f.apply(this, arguments);
		}
		return oCache[sKey];
	}
	return fu
}

/**
 * @type {function(Number)}
 * @return {Number}
 * @properties={typeid:35,uuid:"07B79A37-F9B1-457D-B491-CD8E4D3AD534",variableType:-4}
 */
var fib = memo(function(n) {
	switch (n) {
	case 0:
		return 0;
	case 1:
	case 2:
		return 1;
	default:
		return fib(n - 1) + fib(n - 2)
	}
})

Paul

jgarfield:
However, your implementation loses the benefits of wrapping the fib function in the memoizer. ```
var fib = memo(function fib(n) {…})

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.

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)
					}
}

scopes.UPS.rate.shop(data,callback)

Very interested in how others are using closures which is why I checked your code out (no, it wasn’t your rugged good looks or ability to drink me under the table… :twisted: ). So just digging a little further here.

Edit: the memorizer part I don’t get at all so that maybe why I’m a little confused.

Paul:

That’s very close to what I’m looking for! Changing it in that fashion does get rid of the anonymous function warning, HOWEVER, now the problem is Servoy is not using the JSDocs to correctly generate warnings if I don’t provide the correct arguments.

See the following slightly modified version of your code to see what I mean

/**
 * @properties={typeid:24,uuid:"84C1BD5F-EE1F-4F6F-A681-13D94A332E11"}
 */
function doTheCalc() {
  fib();  //Uh oh, no warning of incorrect arguments!
}

/**
* @type {function(Number):Number}
* @param {Number} n
* @return {Number}
* @properties={typeid:35,uuid:"07B79A37-F9B1-457D-B491-CD8E4D3AD534",variableType:-4}
*/
var fib = memo(function(n) {
   switch (n) {
   case 0:
      return 0;
   case 1:
   case 2:
      return 1;
   default:
      return fib(n - 1) + fib(n - 2)
   }
})

/**
* Provide a memoized version of a function
* @param {Function} f
* @return {Function}
* @properties={typeid:24,uuid:"2C6DF5DF-7022-4774-B707-05304853334F"}
*/
function memo(f) {
   var oCache = { };
   /**
    * @type {function(...*):*}
    * @param {...*} arg
    * @return {*}
    */
   var fu = function(arg) {
      var sKey = JSON.stringify(arguments);
      if (! (sKey in oCache)) {
         oCache[sKey] = f.apply(this, arguments);
      }
      return oCache[sKey];
   }
   return fu;
}

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;
}

HOWEVER, now the problem is Servoy is not using the JSDocs to correctly generate warnings if I don’t provide the correct arguments.

The fact that you don’t get the proper warnings is due to the incomplete support to type functions and not related to this example AFAICS, see SVY-4744. Note that function typing is also not listed as supported in our wiki: Annotating JavaScript using JSDoc

Paul

PS: agree with James that the Servoy scopes are not closures as David mentions. They are just scopes, similar to the one scope you have in JavaScript in the browser, which would be the global scope.

Thanks for the info Paul.
I can get around this limitation for now by making the decorated function private and providing a “normal” wrapper function that calls it locally. Minus a little bit of overhead for the function call this works.

Would be great to not have to jump through that hoop though, what are the odds we’ll see an update for that?