Composing functions and functional programming

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. Why not join our community , follow the blog, twitter, G+ .You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.



Comments