ra89fi
ra89fi

Reputation: 1245

Can anyone explain what is happening in the code below that is using JS and memoization technique

It'd be very helpful if someone can explain what is happening here step by step in a simple manner. I know memoize() is caching the functions but I need a better understanding. Thank you!

var memoize = function (f) {
  var cache = {};
  return function () {
    var str = JSON.stringify(arguments);
    cache[str] = cache[str] || f.apply(f, arguments);
    return cache[str];
  };
};

var mUser = memoize(function(x){
  return function() {
    return x;
  };
});

var x = mUser(1);
var y = mUser(2);
console.log(x());  //1
console.log(y());  //2

Edit : I'm keeping the original for record. But posting modified code and my understanding of it. I need opinions if I'm right or wrong and some explanation for either.

var memoize = function (injected) {
  var cache = {};
  return function closure_with_access_to_cache () {
    var str = JSON.stringify(arguments);
    cache[str] = cache[str] || injected.apply(injected, arguments);
    console.log(cache);
    return cache[str];
  };
};

var memoizeUser = memoize (function injected(a) {
  return function closure_with_access_to_a () {
    return a;
  };
});

memoizeUser();

Let's try to backtrack things.

First thing is, when memoizeUser(); statement is getting executed, what is represented by memoizeUser or which function is getting called first ?

var memoizeUser = ... is a function expression, means it is not getting hoisted.

So, memoize is called.

But, var memoize = ... is a function expression too. Looking at it carefully, it is a closure closure_with_access_to_cache and receiving the param passed to memoizeUser when it is called.

Inside this closure_with_access_to_cache, first time, cache is empty so injected.apply(injected, arguments) is executed and got another closure closure_with_access_to_a as returned value. This value is stored to cache and then returned. So, memoizeUser actually becomes closure_with_access_to_a with a equal to the value passed to memoizeUser.

Let's look at some calls and logs.

console.log(memoizeUser());
{ '{}': [Function: closure_with_access_to_a] }
[Function: closure_with_access_to_a]

Cache key is empty object because nothing was passed as param to memoizeUser(). memoizeUser() returns function closure_with_access_to_a which is logged.

console.log(memoizeUser()());
{ '{}': [Function: closure_with_access_to_a] }
undefined

memoizeUser() returns function closure_with_access_to_a which is called and logged undefined which was the value of a as nothing was passed to memoizeUser.

memoizeUser(1);
{ '{"0":1}': [Function: closure_with_access_to_a] }

Like above except a had value of 1.

console.log(memoizeUser(1)());
{ '{"0":1}': [Function: closure_with_access_to_a] }
1

Like above except a had value of 1.

Upvotes: 5

Views: 977

Answers (2)

jpuntd
jpuntd

Reputation: 902

What does it mean that memoize() is caching the functions? It means that the functions do not get executed, but instead a cached result is returned. The result of a (pure) function is always based on its arguments. If you call a function with the same arguments, it will return the same result. That's why we can store the results in the cache object with the arguments (or stra stringified representation of it) as the key and the result of the function as the value:

cache[str] = f.apply(f, arguments);

apply is used to apply function f to an array of arguments we provide, in this case all the arguments our f function was called with.

cache[str] = cache[str] || f.apply(f, arguments);

is a shorthand way to set a default value for cache[str]. If cache[str] is not set, then the function f will be called and the result stored.

The function being memoized in your example is interesting as well. It takes one argument and it returns a function. x and y are functions. You can call them by adding (), the empty argument list. That's why the last lines read

console.log(x());  //1
console.log(y());  //2

In JavaScript, if you use the function keyword inside another function, you are creating a closure.

Because of the closure, the local variable (the local variable x) can remain accessible after returning from the function that is called.

When the function mUser is called with 1 as an argument the local variable x is equal to 1. The function that is being returned captures this state of affairs inside what is called a closure. A closure is a way of storing the values of local variables together with a function.

Upvotes: 1

Paul
Paul

Reputation: 36319

So essentially what this does is make sure that any given function will only be executed one time for any given set of arguments you pass to it.

The memoize function, when executed for a given function, will return a new function that has that cache in context. The first thing it does is create a JSON string representation of the arguments object to use as the unique key for that particular result.

Then it uses the null coalescing operator to either set that cache value to itself (if it already exists) or to the results of the injected function with those arguments applied.

This makes more sense if you actually name all the functions you're playing with:

function memoize(injectedFunction) {
  var cache = {};
  return function memoizedFunction() {
    // 'arguments' here is the arguments object for memoizedFunction.
    var cacheKey= JSON.stringify(arguments);

    // This is a logical OR which is null coalescing in JS. If cache[str] 
    // is null or undefined, the statement proceeds to call injectedFunction.apply. 
    // If it's not, then it returns whatever is stored there.
    cache[cacheKey] = cache[cacheKey] || injectedFunction.apply(injectedFunction, arguments);  
    return cache[cacheKey];
  };
};

So in essence, consider a simple addition function being injected:

function add(a, b) { return a + b; }

var memoizedAdd = memoize(add);

console.log(memoizedAdd(1, 2));

At this point, memoizedAdd runs with the arguments 1, 2. This creates a cacheKey of "{\"0\": 1, \"1\": 2}", and then sets cache[cacheKey] = add.apply(add, [1 ,2]) (effectively, of course inside there it thinks of 'add' as 'injectedFunction'.

The next time you run memoizeAdd(1,2), you get the same result of 3 out of the cache without it running the add() function again.

Hope that all makes sense.

Upvotes: 1

Related Questions