Thursday, 13 October 2016

More than one way... and the joys of the reduce method.

I've been doing the odd bit of tutoring for people learning Javascript because it's always nice to learn something new by teaching it. I recently had a case when someone was tasked with refactoring some code from function declarations to function expressions. This has always interested me so I was more than happy to help. The example provided had a function called square which returned the one parameter multiplied by itself. Simple as, though I did wonder why we weren't using fat arrow syntax as that would've made it even cleaner:

var square = x => x * x;

More properly we had:

var square = function(x) {
    return x * x;
};

Anyway, that wasn't what was required; what was required was for a function which iterated over an array and returned the sum of all of the squares of each element in the array. This was the original function which required refactoring:

function sumSquares(numbers) {
    var total = 0;
    for (var i = 0; i < numbers.length; i++){
        total += numbers[i] * numbers[i]
    }
    return total;
}

console.log(sumSquares([1,2,3])); // 14

For some reason, the for loop needed to be replaced with a function called each which looks like this:

function each(array, func) {
    for (var i = 0; i < array.length; i++) {
        func(array[i]);
    }
}

Which is cool, but I quite like for loops, and while loops for that matter... ahh well.

So we were left with something like this:

function sumSquares(numbers) {
    var total = 0;
    each(numbers, function(element){
        total += square(element)
    });
    return total;
}

console.log(sumSquares([1,2,3])); // 14

Which works but I'm not overly happy with it. I can sort of see what the task is trying to get the student to understand but I'm not sure if it goes far enough, especially with the introduction of the array reduce method and most especially with its polyfill for when you're targeting an ancient browser:

// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
if (!Array.prototype.reduce) {
  Array.prototype.reduce = function(callback /*, initialValue*/) {
    'use strict';
    if (this === null) {
      throw new TypeError('Array.prototype.reduce called on null or undefined');
    }
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }
    var t = Object(this), len = t.length >>> 0, k = 0, value;
    if (arguments.length == 2) {
      value = arguments[1];
    } else {
      while (k < len && !(k in t)) {
        k++; 
      }
      if (k >= len) {
        throw new TypeError('Reduce of empty array with no initial value');
      }
      value = t[k++];
    }
    for (; k < len; k++) {
      if (k in t) {
        value = callback(value, t[k], k, t);
      }
    }
    return value;
  };
}

Anyway, my preferred quick and dirty refactoring would be this:

console.log([1,2,3].reduce((p, c) => p + (c * c), 0));

It's always nice when you can replace a shed load of stuff with a single liner, I blame Empire of Code for getting me that way ;-)

We can test it easily enough using this:

console.log([1,2,3].reduce((p,c)=>p+(c*c),0) === (1*1)+(2*2)+(3*3)); // true

I guess that the beauty of Javascript is that there are so very many ways of doing any one thing. We could re-write it using typescript like this:

var sumSquares = (arr: number[]): number => arr.reduce((a: number, b: number) => a + (b * b), 0);
console.log(sumSquares([1, 2, 3])); // 14

Which would get compiled to this Javascript:

var sumSquares = function (arr) { return arr.reduce(function (a, b) { return a + (b * b); }, 0); };

console.log(sumSquares([1, 2, 3])); // 14

Anyway, that was fun and I learnt something - not least that there wee drop of madness where the function gets called within the each function - I'm going to have to ponder that bit some more as I originally just wanted to pass the square function as the second parameter but then got confused in that it didn't return anything and ended up re-writing it so that it did, adding it as a call within a function works a treat though.