0

I have a bunch of functions that combines each other to return new objects. Some functions return sets composed of return values of these other functions. I want to get the same object reference out of these functions for same arguments, so the set doesn't contain duplicates.

type A = { a: number, b: number };

type A2 = { c: A, d: A };

function makeA(a: number, b: number): A {
    return {
        a, b
    };
}

function makeA2(c: A, d: A): A2 {
    return { c, d };
}

console.log(makeA(2, 3) === makeA(2, 3)); // I want this to be true

let res = [
makeA2(makeA(1,1), makeA(1,2)),
makeA2(makeA(1,1), makeA(1,2)),
makeA2(makeA(1,1), makeA(1,2)),
makeA2(makeA(1,1), makeA(1,2))
];

console.log(new Set(res)); // so I get a single element in this list

I've tried building a utility function called DB that keeps a map the two arguments that the functions take to the return value of the function, and on second call return from the cached value, but I am still getting duplicates, I think I am missing to wrap some functions, or there is a fundamental flaw in my logic.

eguneys
  • 5,048
  • 7
  • 24
  • 47
  • Does this answer your question? [How to customize object equality for JavaScript Set](https://stackoverflow.com/questions/29759480/how-to-customize-object-equality-for-javascript-set) – Noah Mar 20 '21 at 01:38

1 Answers1

1

Yes, you would need to cache the return values of makeA and makeA2 so that the same inputs result in the same outputs. There are a number of ways to do this, but the important thing is to make sure that you are identifying "the same inputs" in robust way.

Here's one possible implementation, which maintains a separate cache for each function. There's some code duplication here, so there's an opportunity to streamline it, but the point is to show something that works:

function makeA(a: number, b: number): A {
    const key = a + ";" + b;
    let ret = makeA.cache.get(key);
    if (!ret) {
        ret = { a, b };
        makeA.cache.set(key, ret);
    }
    return ret;
}
makeA.cache = new Map<string, A>();

function makeA2(c: A, d: A): A2 {
    const key = c.a + ";" + c.b + ";" + d.a + ";" + d.b;
    let ret = makeA2.cache.get(key);
    if (!ret) {
        ret = { c, d };
        makeA2.cache.set(key, ret);
    }
    return ret;
}
makeA2.cache = new Map<string, A2>();

For makeA, the "same inputs" are determined by concatenating the string representation of the two numeric inputs with a semicolon between them to get a string-valued lookup key. For makeB, I'm doing this with the four individual numeric subproperties. Technically, you should only need to use the "identities" of the c and d objects, but doing this is more tedious (you might end up maintaining a Map<A, Map<A, A2>> instance which is ) and doesn't necessarily buy you much. If someone calls makeA2({a: 1, b: 2}, {a: 1, b: 2}) with object literals, do you want the result to be different from if someone calls it like const a = {a:1, b:2}; makeA2(a, a)? Probably not, if your goal is not to proliferate instances.

In each case we look up the key in the cache; if it's there, great. If not, we create the entry at that key and return that.


Let's see if it works:

console.log(makeA(2, 3) === makeA(2, 3)); // true

let res = [
    makeA2(makeA(1, 1), makeA(1, 2)),
    makeA2(makeA(1, 1), makeA(1, 2)),
    makeA2(makeA(1, 1), makeA(1, 2)),
    makeA2(makeA(1, 1), makeA(1, 2))
];

console.log(new Set(res).size); // 1

Looks good.

Playground link to code

jcalz
  • 125,133
  • 11
  • 145
  • 170
  • Instead I built a map of maps that goes from argument a to argument b to value of the make function. This works for both A and A2. But why am I still getting duplicates, I am guessing somewhere in the function chain, I don't return from the cache and make a new object. How do I detect that with the type system – eguneys Mar 20 '21 at 01:51
  • You can't detect reference equality or inequality in the type system, and without a [mcve] of your problem I don't know I have much to suggest. – jcalz Mar 20 '21 at 01:53