Using a WeakMap to track function invocations

79 views Asked by At

In order to learn about using WeakMap, I came up with the following example to track function invocations:

var map = new WeakMap();

function countFunctionInvocations(func) {
    return function(...args) {
        map.set(func, (map.get(func) || 0)+1);
        return func.apply(this, args);
    }
};


const _add = (x,y) => x+y;
const _inc = x     => x+1;


const add = countFunctionInvocations(_add);
const inc = countFunctionInvocations(_inc);

add(2,3);
add(1,2);
add(2,3);
add(2,3);
inc(inc(1));
console.log(map.get(_add), map.get(_inc));
// 4 2

What would be a cleaner way to implement this, as I have to alias the functions back and forth, for example from add to _add and back to add. Additionally, is the above a legitimate usage of Weakmap?

1

There are 1 answers

2
CertainPerformance On BEST ANSWER

The use of the WeakMap is pretty weird - as you've noticed, if you want to look things up with it with functions, you have to store those functions in variables, which are separate from the count-invokified function. It makes things awkward and a WeakMap doesn't seem to have a net benefit over a plain object, as long as you don't have function name collisions.

If this is allowed by what you're looking for, you could pass another argument to countFunctionInvocations indicating the name, allowing you to pass a (concise, anonymous) arrow function.

const counts = {};

function countFunctionInvocations(name, func) {
    return function(...args) {
        counts[name] = (counts[name] ?? 0) + 1;
        console.log("called", name, counts[name]);
        return func.apply(this, args);
    }
};

const add = countFunctionInvocations('add', (x,y) => x+y);
const inc = countFunctionInvocations('inc', x => x + 1);

add(2,3);
add(1,2);
add(2,3);
add(2,3);
inc(inc(1));
console.log(counts.add, counts.inc);

Additionally, is the above a legitimate usage of Weakmap?

I mean, it could be used, but as we've noticed - it's awkward to have to have two separate references to a function - one being the base function, and one being the one transformed by countFunctionInvocations.

That said, this is certainly a legitimate usage of WeakMap over a Map, because it allows the function (and its invoke count) to be garbage collected once nothing else can reference it.

I guess another option would be for the returned function to be the one put into the Map, so that you only have one identifier on the outside, but it'd require a function or a second argument to indicate the name of the function, otherwise the inside of countFunctionInvocations would only see an anonymous function without a name.

const map = new WeakMap();

function countFunctionInvocations(func) {
    const fn = (...args) => {
        map.set(fn, (map.get(fn) || 0) + 1); // use returned function in Map
        return func.apply(this, args); // invoke passed function
    }
    return fn;
};

const add = countFunctionInvocations(function add(x, y) { return x + y });
const inc = countFunctionInvocations(function inc(x) { return x + 1 });

add(2,3);
add(1,2);
add(2,3);
add(2,3);
inc(inc(1));
console.log(map.get(add), map.get(inc));
// 4 2