In JavaScript currying and functional programming I looked at an Apps Script example using currying (embedding values that would normally be arguments in a function). Another functional programming topic that’s becoming popular is the idea of composition. In React, Polymer and other similar frameworks, composable elements promote re-use. Composing functions has the same objective.
Why not compose ?
First of all, it’s quite complicated, but once you have a general-purpose composer, it’s a fairly repeatable experience as long as you do it often and can remember what you’re doing.
Secondly, it’s a lot harder in pre-ES6 JavaScript, which is where we are with Apps Script.
What is composing?
It’s a way of creating a function that applies a series of function an evolving result. You could think of it like this
var result = f0(f1(f2(f3(f4(arg)))));
A composed version of this would be
compose (f0,f1,f2,f3,f4) (arg);
or more likely
var f = compose (f0,f1,f2,f3,f4) ; f (arg);
The idea being that you could re-use each of the component functions in various combinations as required, including library functions, for example
compose (myFunction , Math.sqrt , parseFloat ) ("3.9");
Would execute as
var result = myFunction ( Math.sqrt ( parseFloat ("3.9")));
Why bother?
I wrote this article as a learning exercise for me, and I’m not entirely sure how I feel about the whole topic yet. In any case, let’s plough on an see what develops
Arguments
A function can only return a single value, so that means that each function in the stack should only be expecting one argument, so objects may be required to pass through complex data from the innermost function. I guess there may be a way round this but I haven’t figured it out. Be glad to have your ideas on the forum.
General composer
First off, I need a composer function. This looks pretty obscure, but I’m pretty happy with it, and I’ll try to walk through it.
function composer() { return Array.prototype.slice.call(arguments).reduceRight(function (prev, current) { return function () { return current(prev.apply(undefined, arguments)); }; }); };
It’s expecting an argument list of functions which it will apply starting at the last one (this would be the innermost function), and will return a function that applies each of the functions in the list successively, so
return Array.prototype.slice.call(arguments).reduceRight(function (prev, current)
simply converts the arguments into a real array so I can use reduceRight (starting at the last argument) to execute each one in turn
return function () { return current(prev.apply(undefined, arguments)); };
calls the current function – the arguments to it are the result of calling the previous function with the result so far.
So the output of the composer function is another function that successively calls each of the functions that were passed to it.
Example
To make this a slightly meatier example, I’m going to compose a function that consists of several pieces of Apps Script.
- Opens a spreadsheet with a given id
- Opens a sheet with a given name
- Gets the data in that sheet
- Totals the data in a given column.
var sumColumn = composer ( // add all the values in column function (ob) { // miss the heading row return ob.values.slice(1).reduce (function (p,c) { return p+c[ob.package.column]; },0); }, // get the values function (ob) { return {values:ob.sheet.getDataRange().getValues(), package:ob.package}; }, // get the sheet function (ob) { return {sheet:ob.spreadsheet.getSheetByName (ob.package.sheetName), package:ob.package}; }, // get the spreadsheet function (package) { return {spreadsheet:SpreadsheetApp.openById(package.id), package:package}; } );
Notice that the functions are specified in the order outer to inner (with the inner executed first), and that I’m passing an object as the argument and the result so that these can be chained together and pass through the initial arguments.
And now I can use it like this
Logger.log ( sumColumn ({ id:'1181bwZspoKoP98o4KuzO0S11IsvE59qCwiw4la9kL4o', sheetName:"dupRemove", column:2}) );
And the result
41.0
Using this data
Is it worth it?
On the plus side, I now have various functions that perform small tasks, which I could re-use, recompose with other things, amend etc. as well as a general purpose composer.
On the other hand, for this task, I could have just done
Logger.log ( SpreadsheetApp.openById (id) .getSheetByName(sheetName) .getDataRange() .getValues() .slice (1) .reduce (function (p,c) { return c[column]+p; },0));
It has been interesting though and I’d be interested to hear how you’d use something like this