Sunday 23 August 2020

Method Composition

Every now and then I'll find something that will remind me again of how beautiful a language JavaScript is. This time I came up with a not much frequent need, Method Composition. Function Composition is quite popular, and libraries like lodash-fp with its Auto-Curried, iteratee-first, data-last approach make composition really powerful.

In this case I'm talking about composing methods. May seem a bit odd, but I came up with a case where it could be rather useful. I have several methods that modify (yes, in spite of the immutability trend I continue to use mutable objects on many occasions) the object on which they are invoked, and I'm calling several of them in a row, to the point where it would be useful to add a new method to the class that just performs all those consecutive calls. Adding a new method to the "class" is so easy as adding the new function to the prototype of the "class" (if I just wanted to add it to an specific instance I would add it to the instance __proto__ (or instance [[Prototype]])). In order to invoke the sequence of methods we have to bear in mind that each method can have a different number of parameters, but Function.length comes to our rescue.

It's more useful to just see the code than further explaining it, so here it goes the factory function that composes the different methods:

 

function composeMethods(...methods){
    //use a normal function, not an arrow, as we need "dynamic this"
    return function(...args) {
        methods.forEach(method => {
            let methodArgs = args.splice(0, method.length);
            method.call(this, ...methodArgs);
        })
    }
}

And given a class like this:

 

class Particle{
    constructor(x, y, opacity){
        this.x = x || 0;
        this.y = y || 0;
        this.opacity = 1;
    }

    move(x, y){
        this.x += x;
        this.y += y;
    }

    fade(v){
        this.opacity += v;
    }
}

We'll use it this way to compose the "move" and "fade" methods into a new "moveAndFade" method:

 

//composing "move" and "fade" and adding it as a new "moveAndFade" method
Particle.prototype.moveAndFade = composeMethods(Particle.prototype.move, Particle.prototype.fade);

let p1 = new Particle();
console.log(JSON.stringify(p1));

//x, y, v
p1.moveAndFade(4, 8, -0.1);
console.log(JSON.stringify(p1));

p1.moveAndFade(2, 3, -0.2);
console.log(JSON.stringify(p1));

// {"x":0,"y":0,"opacity":1}
// {"x":4,"y":8,"opacity":0.9}
// {"x":6,"y":11,"opacity":0.7}


I've uploaded it into a gist.

No comments:

Post a Comment