medikoo / memoizee

Complete memoize/cache solution for JavaScript
ISC License
1.73k stars 61 forks source link

Filtered array memoization? #48

Closed peteruithoven closed 8 years ago

peteruithoven commented 8 years ago

I'm trying to get create a memoized function that picks "selected" items out of a ...byId object. I'm hoping to cache the result when the selection didn't change and when the selected objects didn't change. This is important because I'd like to pass the result to other (expensive) regularly memoized functions. Not memoized example:

const state = {
    objectsById: {
        a: { health: 1, ... },
        b: { health: 0.5, ... },
        c: { health: 1, ... }
    }, 
    selection: ['a', 'b']
};
function getSelectedObjects(state) { // how to memoize? 
    return state.selection.map(({id}) => state.objectsById[id]);
}

I can implement a crude custom shallow array "memoizer", but is there a way to do this with memoizee? I'd like to benefit from it's other features. Is there for example a way to implement a custom compare function?

An crude implementation of the desired behaivior:

const state = {
    objectsById: {
        a: { health: 1 },
        b: { health: 0.5 },
        c: { health: 1 }
    }, 
    selection: ['a', 'b']
};

function getSelectedObjectsTransform(state) {
    return state.selection.map(id => state.objectsById[id]);
}
const getSelectedObjects = arrayMemoizer(getSelectedObjectsTransform);
getSelectedObjects(state));

function arrayMemoizer(fn) {
    let prevArg; 
    let prevResult;
    return function(arg) {
        // if same arguments; return prev result; 
        if(arg === prevArg && prevResult) {
            return prevResult;
        }
        prevArg = arg;

        const result = fn(arg);
        // if no prevResult; return new result
        if(!prevResult) {
            return prevResult = result;
        }
        // if length changed; return new result
        if(result.length !== prevResult.length) {
            return prevResult = result;
        }
        // if item in array changed; return new result 
        for(let index in result) {
            let item = result[index];
            let prevItem = prevResult[index];
            if(item !== prevItem) {
                return prevResult = result;
            }
        }
        // return prev result
        return prevResult;
    }
}
medikoo commented 8 years ago

@peteruithoven yes, it's not perfectly documented, but it's possible to provide custom normalizer function. It's function that should return an unique id for received arguments, and on that basis internal logic will decide whether given case was memoized or not.

In your case it might work well if done as follows:

var memoized = memoize(getSelectedObjectsTransform, {
  normalizer: function (args) {
    var state = args[0];
    return JSON.stringify(state);
  }
});

I'm closing it, as it's not a bug report or feature request

peteruithoven commented 8 years ago

@medikoo Thanks for the idea, but that doesn't really use the benefit of immutable data? I'd like to find a solution without a JSON.stringify(). I'm afraid the id will get very big, when I have a lot of data, I'm not sure what kind of negative impacts that would have.

medikoo commented 8 years ago

@peteruithoven in any case unique string id needs to be resolved. If you know the way how to make it short, just do it :) I don't know internal implications of your project, so it's hard for me to help you more on that case.

Alternatively if for same state structures always same object instances are passed, then you do not need to provide any normalizers, as by default memoization works by checking whether objects are equal, resolved ids are very short in that case.