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

For more like this see Google Apps Scripts Snippets