0

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?

David542
  • 104,438
  • 178
  • 489
  • 842

1 Answers1

1

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
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • thanks for all this. Is the above a stretch for using the `WeakMap` data type? Are there any consistent/legitimate uses for it, or is it quite rare in production programs? – David542 May 17 '22 at 14:25
  • 1
    It used to be one of the few ways for classes to have good privacy for instance properties via a closure, but that's changed now that private properties are an option. (It's used commonly in transpiled code with Babel) Other than that, I've never encountered a situation when writing my own code where a WeakMap seemed like the right solution - but that's partially a result of my personal style, and I wouldn't rule out never finding it to be the right tool for a job in the future. – CertainPerformance May 17 '22 at 15:47