JavaScript currying and functional programming - even more

JavaScript currying and functional programming was an introduction to currying. A very useful technique that uses the properties of JavaScript closures - how, where and why to encapsulate argument values to create a new version of a function. 

The examples in that were fairly specific. This article will provide a flexible general purpose currying algorithm that you can use to curry anything. The function is available in my cUseful library - but more on that later.

Examples

Sticking to the same kind of examples, using the Properties Service in Apps Script, you might find yourself with a useful function that looks like this. 
  /**
   * general function to set key/value in given store
   * @param {string} store script, document or user
   * @param {string} key the key
   * @param {number|string|boolean} the value to set
   * @return {PropertiesStore} the store used
   */
  function setPropertyValue (store , key , value) {
    var store = PropertiesService[
      'get' + 
      store.slice (0,1).toUpperCase() + 
      store.slice(1).toLowerCase() + 
      "Properties"
    ]();
    store.setProperty (key , value);
    return store;
  }

that can be called like this, to set a key/value pair in a given store
 setPropertyValue ("script" , "preferredVariety" , "biryani");

But you can use currying to store some or all of these arguments, so all of these variations do the same thing
  curry (setPropertyValue, "script") ( "preferredVariety" , "biryani");
  curry (setPropertyValue) ("script" , "preferredVariety" , "biryani");
  curry (setPropertyValue) ("script") ( "preferredVariety" , "biryani");
  curry (setPropertyValue) ("script", "preferredVariety" ) ("biryani");
  curry (setPropertyValue) ("script", "preferredVariety" , "biryani");
  curry (setPropertyValue, "script", "preferredVariety" )( "biryani");

That may not seem very useful at first glance, but remember what currying does for you. It creates a brand new function from a function you pass to it, and bakes in some of the arguments you pass to it, so you don't need them any more. 

Assuming that the curry function is from the cUseful library, get a shortcut for it first.
  var curry = cUseful.Utils.curry;

Let's say that you always want to use the ScriptProperties service, you can shorten your general setPropertyValue function to build in the "script" argument into a new function like this.
  var setCurriedScript = curry (setPropertyValue , "script");
  setCurriedScript ("preferredVariety" , "bhuna");

Take a bit further, and create a function that has both the "script" store and the "preferredVariety" key already built in.
var setPreferred = curry (setPropertyValue , "script","preferredVariety");
setPreferred ( "korai");

And you can even recurry already curried functions
  var setPreferred = setCurriedScript("preferredVariety");
  setPreferred ( "dhansak");

Now we'll update the underlying script to make it handle objects too.
/**
   * general function to set key/value in given store
   * @param {string} store script, document or user
   * @param {string} key the key
   * @param {*} the value to set
   * @return {PropertiesStore} the store used
   */
  function setProperty (store , key , value) {
    var store = PropertiesService[
      'get' + 
      store.slice (0,1).toUpperCase() + 
      store.slice(1).toLowerCase() + 
      "Properties"
    ]();
    // maybe it's an object
    if (typeof value === "object") {
      value = JSON.stringify (value);
    }
    // and might as well exponential back off it
    cUseful.Utils.expBackoff ( function () {
      return store.setProperty (key , value);
    });
    
    return store;
  }

Now curry that and use it for setting objects to the store
 //now curry that
  var setFavorite = curry (setProperty , "script" , "favorite");
  setFavorite ({
    name:"vindaloo",
    spices:[
      "coriander", 
      "cumin", 
      "cinnamon", 
      "mustard", 
      "cayenne", 
      "cardamom", 
      "turmeric", 
      "black pepper" ,
      "cloves"
      ]
  });

Libraries and avoiding permission requests

Another handy thing this gives you is that you can create libraries that don't know the details of what they are doing, and don't need a long list of arguments. In Apps Script, if you access some Services it will prompt a request for access to some resource. This means that if you mention for example PropertiesService in a library, everybody that uses it will be asked for permission even though they are not using the part of the library that access properties services. To make dependency free libraries, you can curry things like this in and pass it to the library. 

Imagine I have a library that does various things, including this
var MyLib = (function (ns) {

  ns.setSpices = function (setFavorite , name , spices) {
    return setFavorite ( {
      name:name,
      spices:spices
    });
    
  };
  return ns;
})({});

It has to write some data to a property store against a given key, but you don't want to pass the property store explicitly, and neither you even want to expose the key against which to write the data, so instead you pass over a curried function that has all that built in. Like this, third party libraries don't have access to your property store - yet are able to write to a specific key in it!
  // handy to pass to a library so the library doesn't need to get property permissions
  MyLib.setSpices (curry (setProperty , "script" , "favorite") , "dhanask" , ["turmeric", "cinammon", "cardamom"]);



The curry function


You can get this in the cUseful library (1EbLSESpiGkI3PYmJqWh3-rmLkYKAtCNPi1L2YCtMgo2Ut8xMThfJ41Ex) or on github

It's very concise, but a little involved. You can simply use it, but it's included below in case you want to get into how it works. 

It's exposed by this Utils namespace method, which simply calls the curry function, passing on any arguments. 
  ns.curry = function () {
    return curry.apply ( null , Array.prototype.slice.call (arguments));
  }

The curry function is expecting at least a function. The function itself is extracted from the argument list, and the remainder of the arguments are considered to be the arguments of the function. A useful property of a function is function.length, which returns the arity (the number of arguments it is expecting) . If the number of arguments received is the same as the number received then it's time to execute that function normally and return its result, otherwise we need to create a new function, using the curry function itself to work through the arguments.
function curry (func) {
    
    // get the arguments and stop the first
    var args = Array.prototype.slice.call (arguments,1);
    
    // if there's no more, the call the func and we're done
    // otherwise we need to create a new curry function with the latest verstion
    // of the arguments
    return args.length === func.length ? 
      func.apply (undefined , args) :
      curry.bind.apply ( curry , [this , func].concat (args));  
    
  };
  
And that's a wrap for this deeper dive to currying. If you want more, ping me on the community.

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