Compose: functions as building blocks

As regular readers have probably noticed, a recurring theme of these posts is function manipulation as an expressive device. JavaScript treats functions as first class objects, that is they can be created and modified dynamically and passed as data to other functions and objects. Shamelessly continuing this theme, allow me to introduce functional composition…

Here’s a couple of simple examples to start:-

var  alertPower = alert.compose(Math.pow);
alertPower(9,8); //alert shows 43046721
var  roundedSqRoot = Math.round.compose(Math.sqrt);
roundedSqRoot(28); //5

The compose function allows us to define a new function based on two existing (or anonymous) functions. In generic form:

myFunction = function1.compose(function2);

and when we call…

myFunction(myArgs);

…function 2 gets invoked with myArgs and the result is passed to the invocation of function 1. Like curry, compose is not a native JavaScript function but its easy to augment the Function prototype to support it.

Function.prototype.compose  = function(argFunction) {
    var invokingFunction = this;
    return function() {
        return  invokingFunction.call(this,argFunction.apply(this,arguments));
    }
}

Now a meatier example – this creates a rudimentary parseAlpha function (it also makes use of the curry function described in an earlier post) :-

//use curry  to make a slice(0,x) function, then use compose to wrap it around  search.
var sliceToRegEx =  String.prototype.slice.curry(0).compose(String.prototype.search);

//now curry with a regEx that returns first non alpha character
var parseAlpha = sliceToRegEx.curry(/[^ a-zA-Z]/);
parseAlpha.call("Pork Bellies #45678"); //Pork Bellies

Compose and curry often work well together – forging new functions from old, in a concise and readable way. Here’s another example:-

var  queryString =  String.prototype.substring.compose(String.prototype.indexOf).curry('?');
queryString.call("http://www.wunderground.com?query=94101&weekday=Tuesday");  //?query=94101&weekday=Tuesday

Going deeper, the following function loops through an Enumerable looking for the longest sequence for which the given function holds true (notice I’m ignoring blank string members) :-

var longestSequence = function(compareFunc,myEnum) {
    var result = {member:null, count:0};
    var thisCount = 1;
    for (var i=1; i<myEnum.length; ++i) {
        if ((myEnum[i]!==" ") && compareFunc(myEnum[i-1], myEnum[i])) {
            if (++thisCount >= result.count) {
                result = {member: myEnum[i], count: thisCount};
            }
        } else {
            thisCount = 1;
        }
    }
    return result.member + " (" + result.count + ")";
}

So for example, to look for the longest successive run of equal members

longestSequence(function(a,b){return  a==b},'skiing'); //i (2)

We can use curry to create a reuseable function to get the longest run of the same member.

var  longestEqualRun = longestSequence.curry(function(a,b){return a==b});
longestEqualRun([1,1,2,2,2,2,3,3]); //2 (4)

Now here comes compose…and voilà…we have a function to return the most frequent member:-

var  mostFrequent = longestEqualRun.compose(function(myEnum){return myEnum.split('').sort()});
mostFrequent("The quick brown fox jumps over the lazy dog"); //o  (4)

How about a function to return the most frequent character in the current page source? No problem – just compose once more:-

function getInnerText(elem) {
    return elem.innerText || elem.textContent;    
}

var  mostFrequentInPage = mostFrequent.compose(function() {return getInnerText(document.body)});
mostFrequentInPage(); //e (263)
About these ads

14 thoughts on “Compose: functions as building blocks

  1. Pingback: Webdev Weekly #15 | samuli.hakoniemi.net

  2. Pingback: uberVU - social comments

  3. Pingback: [번역] 함수 선언 vs 함수 표현 (Function Declarations vs. Function Expressions) | Inside jQuery

  4. I am working on the next version of jPaq and you have just inspired me to add the compose function to it. I knew there was something that I liked about the compose function, but I couldn’t remember what it did until I saw the example in your post. Thanks for the reminder.

  5. I ported it to CoffeeScript.

    Function.prototype.compose = (argF) -> => @(argF(arguments…)…)

    When I program Scheme I use compose every day. In CoffeeScript there is usually a workaround (because of the destructuring splats) but I wanted to have it available.

    • Hi Sandra

      ThIs is great!
      There is an issue if your fn argument does not return an array (which is weird because you are using a splat)

      So this works:

      Function.prototype.compose = (argF) -> => @(argF(arguments…)…)
      add = (a) -> a + a
      mult = (a) -> [a * a]
      addMult = add.compose(mult)
      addMult(4)
      //32

      But this doesn’t (because apply expects an array arg)

      Function.prototype.compose = (argF) -> => @(argF(arguments…)…)
      add = (a) -> a + a
      mult = (a) -> a * a
      addMult = add.compose(mult)
      addMult(4)

  6. Pingback: 为什么我们要学习Haskell这样的编程语言 | 极地 EA163

  7. Pingback: 为什么我们要学习Haskell这样的编程语言 - 博客 - 伯乐在线

  8. Hi! What do you think, is it possible or not to use compose with String’s slice and another function (and probably also search() ) that returns 2 arguments at one time? i.e:

    var A =String.prototype.slice.compose((function(){ /*returns 2 parameters for slice*/ }) );

    So as to build a function acting like substring, if you have “aaaBBBccc” and you call likeSubstring(‘B’,’c’) the result will be “BBB”. Maybe curry would be usefull too?

  9. dear,

    thanks a lot for your wonderful articles!
    After reading the “composition” technique I thought: “wonderful! but how can I remove the ‘.call’ on ‘parseAlpha.call’? ”

    After some tries I reached this solution which I would share with you:
    ====================================================
    function toArray(en) {
    return Array.prototype.slice.call(en);
    }

    Function.prototype.curry = function() {
    if (arguments.length<1) {
    return this; //nothing to curry with – return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
    return __method.apply(this, args.concat(toArray(arguments)));
    }
    }

    Function.prototype.compose = function(argFn) {
    var fnToCall = this;
    return function() {
    return fnToCall.call(this, argFn.apply(this,arguments));
    }
    }

    Function.prototype.retAsABundle = function() {
    var _fn = this;

    return function() {
    return {
    ctx: this,
    vals: _fn.apply(this, arguments)
    };
    }
    }

    Function.prototype.applyBundle = function(fn2bundle) {
    var _fn2bundle = fn2bundle;
    var _fn = this;

    return function() {
    var bundle = _fn2bundle.apply(this, arguments);
    return _fn.call(bundle.ctx, bundle.vals);
    }
    }

    var fnExtractTillIdx = String.prototype.slice.curry(0);
    var regex = '[^ a-zA-Z]';

    var fnSearchWithRegex = function(expr) {
    var regex = expr;

    return function(str) {
    return String.prototype.search.retAsABundle().call(str, regex);
    }
    }(regex);

    var fnParseAlpha = fnExtractTillIdx.applyBundle(fnSearchWithRegex);

    var str = "a string #45678";
    fnParseAlpha(str); // 'a string '
    ====================================================

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s