Author Topic: Function Composition in Javascript  (Read 767 times)

0 Members and 1 Guest are viewing this topic.

Offline Lanny

  • Zealot
  • ****
  • Posts: 1,123
    • View Profile
Function Composition in Javascript
« on: December 10, 2014, 03:38:43 am »
So I've been doing a pretty big refactor of some client side code at work and one (among many) of the things I'm trying to achieve is a somewhat more functional/declarative style. I noticed that in doing this I had a lot of code that looked like this:

Code: [Select]
var foo = 42
foo = f1(foo)
foo = f2(foo)
foo = f3(foo)

// or
var foo = f3(f2(f1(42)))

neither of which are particularly pretty, so I wrote a nifty little way to manage function composition:

Code: [Select]
    function uOr(value, alt) { return (value === undefined) ? alt : value }
    function toArr(arrLike) {
      var result = []

      for (var i=0; i<arrLike.length; i++)
        result.push(arrLike[i])

      return result
    }

    function Chain(data, fnStack) {
      this._data = data
      this._fnStack = fnStack || []
    }

    Chain.prototype = {
      exec: function(data) {
        data = uOr(data, this._data)

        for (var i=0; i<this._fnStack.length; i++) {
          var args = this._fnStack[i].slice(0),
            fn = args.shift(),
            ins = args.pop()

          if (ins) args.unshift(data)
          else args.push(data)

          data = fn.apply(this, args)
        }

        return data
      },
      _push: function(insert, args) {
        var newStack = this._fnStack.slice(0)

        args.push(insert)
        newStack.push(args)

        return new Chain(this._data, newStack)
      },
      ins: function() { return this._push(true, toArr(arguments)) },
      app: function() { return this._push(false, toArr(arguments)) },
    }

So it's just a data structure that maintains a stack of functions that will be applied to and some magic to do a poor man's function currying. So you can do stuff like this:

Code: [Select]
>new Chain(42)
  .ins(function(x) { return x + 2 })
  .ins(function(x) { return x + " is really old" })
  .exec()
"44 is really old"

which is somewhat more manageable when you have a lot of functions in the lineup, and it looks similar enough to jQ's selection chaining that JS programmers seem to be able to grasp it. One of the cool things though is that data can be given at the end of the chain too:

Code: [Select]
>var sumOfSquares = new Chain()
  .ins(map, function(x) { return x*x })
  .app(reduce, function(x, y) { return x + y })
undefined
>sumOfSquares.exec([1, 2, 3, 4])
30
>sumOfSquares.exec([42, 31, 94])
11561

Chains are also immutable (well, so far as anything in JS is)

Code: [Select]
>var squares = new Chain().ins(map, function(x) { return x*x })
undefined
>var sumOfSquares = squares.app(reduce, function(x, y) { return x + y })
undefined
>squares.exec([1,2,3,4])
[1, 4, 9, 16]
>sumOfSquares.exec([1, 2, 3, 4])
30

This is probably just a boring language feature to anyone working in an actual functional language, but it was fun to write and since there's no real equivalent notation in JS I find it's pretty useful in my day to day work