theKashey / memoize-state

The magic memoization for the State management. ✨🧠
MIT License
329 stars 16 forks source link

[question] Accessing a complex object field but then doing nothing with it "binds" the memoization to it #67

Closed javierfernandes closed 2 years ago

javierfernandes commented 2 years ago

Sorry for the long title.

I noticed that accessing a "complex object" field but then doing nothing with it "binds" the memoization to it (iex it is recorded as the affected path)

    it('memoize test', () => {

        const memoized = memoize(context => {
            context.complex  // access but then do nothing with it
        })

        // call it
        memoized({ complex: { a: 1, b: 2, c: 3, d: 4 } })

       //  assert
        expect(memoized.getAffectedPaths()[0])
               .toEqual([])

      })

The assertion fails because it gets bind to ".complex".

Although if the fn changes and it access one of its inner member then affectedPath only contains the leaf longer path For example if the function does

        const memoized = memoize(context => {
            context.complex  // access but then do nothing with it

            return context.complex.a
        })

Now affectedpath is just .complex.a

<< Ideally >> I would expect it not to bind with .complex since we are clearly not using that object (we are not accessing any member, neither getting its keys/values, etc.)

This example is of course a simplified version. In the real case the memoizing function is actually quite complex (a DSL interpreter). In that case it happens that the code access such a complex field, to pass it as arg to many other nested functions, which depending on other inputs, they might end up not using that complex object at all ! But then the memoization cache misses if that complex object changes.

I was wondering if this is the expected behaviour (I can imagine there might be other cases where this field is used like identity comparisson that cannot be catched by the library right ?? is that the reason?) And if so, if it makes sense to have some kind of option so that for particular cases the user might "tweak" this.

For the previous example

        const options = {
            omitPartialPaths: '.complex',  // here! 
        }
        const memoized = memoize(context => {
            context.complex  // access but then do nothing with it

            return context.complex.a
        }, options)

Currently our fix for this was to force ALWAYS accessing a field, a fake one, so that the library won't ever get coupled with the full "object". For example:

        const memoized = memoize(context => {
           otherFnThatMightNotUseIt(context.complex)

           // force it
          context.complex.___FAKE_ACCESS__

        })

Thanks for your time and congrats for such a good library ! 👍

theKashey commented 2 years ago
context.complex  // access but then do nothing with it
return context.complex.a

This library (and JS proxies in overall) cannot know the fact that they were used or not used, only that they were "read", recording the longest path. So by doing ___FAKE_ACCESS__ you are creating a "longer" path, which will be used for comparisons.

Probably your problem can be solved if memoize can be explicitly told to ignore some objects or named paths, but in your case it's always a little conditional, so might be you should stick to the way hack you've found.

Anyway, I am not really in the proxy-memoisation business for a quite while. Wondering is @dai-shi (https://github.com/dai-shi/proxy-memoize) has any good ideas. AFAIK logic is always the same.

dai-shi commented 2 years ago

Yeah, the same thing in proxy-memoize / proxy-compare.

context.complex  // access but then do nothing with it

☝️ we should have .complex as affected, otherwise this won't work 👇

return context.complex === someCachedObject;
javierfernandes commented 2 years ago

Alright ! thanks guys for your quick response 👍 I'll stick to the fix for this particular case. I'll close this issue then. Still it might help even closed, to anyone having the same doubt/question

Thanks again !