1
\$\begingroup\$

In order to better understand functional programming in Javascript, I thought I'd try to recreate (as well as extend) some of the higher order functions on the Array object. These methods don't intend to be identical but to have the basic functionality. Any tips/suggestions/feedback would be greatly appreciated!

// Array.prototype.map()
Array.prototype.newMap = function(fn) {
    var returnedArray = [];

    for (var i = 0; i < this.length; i++) {
        returnedArray[i] = fn(this[i]);
    }
    return returnedArray;
};

// newMap for multi-dimensional arrays
Array.prototype.newMapMulti = function(fn) {
    var mainArray = [];

    for (var i = 0; i < this.length; i ++) {
        var result = this[i].newMap(fn);
        mainArray.push(result);
    }
    return mainArray;
};

// Array.prototype.filter()
Array.prototype.newFilter = function(fn) {
    var returnedArray = [];

    for (var i = 0; i < this.length; i++) {
        var c = this[i];

        if (fn(c)) {
            returnedArray.push(c);
        }
    }
    return returnedArray;
};
// newFilter for multi-dimensional arrays
Array.prototype.newFilterMulti = function(fn) {
    var mainArray = [];

    for (var i = 0; i < this.length; i++) {
        var result = this[i].newFilter(fn);
        if (result.length === 0) { continue }
        mainArray.push(result);
    }
    return mainArray;
};

// Array.prototype.find()
Array.prototype.newFind = function(fn) {

    for (var i = 0; i < this.length; i++) {
        var c = this[i];

        if (fn(c)) {
            return c;
        }
    }
};

// Array.prototype.reduce()
Array.prototype.newReduce = function(fn) {
    var total = 0;

    for (var i = 0; i < this.length; i++) {
        total += fn(this[i]);
    }
    return total;

};

// newReduce for multi-dimensional arrays
Array.prototype.newReduceMulti = function(fn) {
    var total = 0;

    for (var i = 0; i < this.length; i++) {
        total += this[i].newReduce(fn);
    }
    return total;
};

// returns a reduce value. if fn1 returns a value < x, then apply fn2 and return that value
Array.prototype.newReduceIf = function(fn1, fn2, x) {
    var reduceResult = this.newReduce(fn1);

    if (reduceResult < x) {
        var secondResult = this.newReduce(fn2);
        return secondResult;
    }  
    return reduceResult;
};
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! Good job on your first question. \$\endgroup\$ – SirPython Jan 16 '16 at 2:09
2
\$\begingroup\$

First, it's not recommended to extend native objects. That's because modifying the prototype applies to all instances. Now let's say the spec added a newMap method that acted differently from your implementation. Code that uses newMap according to spec (like third party code) will break because your code overrides the native implementation and operates in a totally different way.

That's one reason why libraries like lodash and underscore don't extend native objects, and just accept the array/object as an argument. They also provide "wrapping" mechanisms, accepting an array and wrapping it in a container object with its own prototype methods. That way, they safely add methods without modifying native objects while keeping it looking like they extend.

// In lodash, the _ function wraps the array in a "lodash object" which
// has methods like map etc.
var newValues = _([1,2,3]).map(v => v * 3).value(); // [3,6,9]

// In jQuery, the $ function wraps the element in a "jQuery object" which
// has its own DOM manipulation methods.
$(document.getElementById('myElement')).remove();

Now let's say you didn't extend it via native objects.

newFilterMulti isn't really a true multi-dimensional array filter. It only appears to go 2 levels deep (2D). Either you make it a true multi-dimensional filter (going as deep as it can) or rename it so it's more apparent that it only goes 2 levels deep.

newReduce doesn't act the same way native reduce works (and this is a good example of spec-breaking implementation). reduce doesn't add return values. It carries over the last result to the next call regardless of type. This means the callback accepts 2 values, the carry and the current value. Also, most reduce implementations accept an initial argument for the carry value. If omited, reduce starts at the second element in the array, with the first element as carry.

Again, newReduceMulti isn't a true multi-dimensional reduce, just 2D. Re-implement or rename accordingly.

Now one technique used by most libraries is to reuse the operations that have already been defined. In the case of jQuery, internally it makes heavy use of its $.each. So if they need anything looped through, instead of tediously writing for loops everywhere, they use their $.each. Also, I heard FP operations rely heavily on map.

So if you could make a solid forEach and map implementation, you can readily reuse them in your other methods instead of writing a for loop every time.

\$\endgroup\$
0
\$\begingroup\$

I'll first comment on your functions before comment on what you're actually doing.


newMapMulti

I have two major, fundamental issues with this:

  1. You're not actually mapping anything. The map function is passed in, but you don't actually use it. If you changed mainArray.push(result) to mainArray.push(fn(result)), then you would be mapping something.
  2. Your code will error, if any of the items in parent array aren't arrays, because you attached your function to the Array.prototype.

By changing your for loop to look like the following, you could avoid that:

for (var i = 0; i < this.length; i ++) {
    var result = this[i];
    if (this[i] instanceof Array){
        result = result.newMap(fn);
    }
    mainArray.push(result);
}

I also have two less important issues with this:

  • i ++, your syntax is wrong here, it should be i++.
  • It's not actually a multi-dimensional array. That would require it to be equally deep as equally long. This is a jagged array.

Using prototypes:

You shouldn't be attaching new functions to primitive prototypes.

What happens if in ES7, they add a function to the array prototype called newMap? And your code is sitting mixed in with production code.

Then Bobby Maintainer tries to use newMaps functionality, and finds it doesn't work the way he expected, because your version is overriding the language specifications' version.

I would avoid using prototypes on primitive types unless it's just practice, don't ever use them in production code. With the exception being polyfills, of course.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.