Wednesday, September 24, 2014

Memoizing arbitrary function using JavaScript Proxies

So I have been playing around with some problems on https://projecteuler.net. They are fairly easy problems, but it has been a way to explore some of the new features of ES6. Notably generators and proxies. These problems often involve series like the Fibonacci series. One technique to solve this computationally is memoizing. For a pure function (one that always returns the same thing for a specific input) you can store a difficult to calculate output in a hash table. So it is simple to write a memoizing Fibonacci sequence, but the simple way is bad code. You mix the calculation with the optimization. I also wanted to be able to re-use the memoization code on other functions. So I looked up my old post on Aspect oriented JavaScript. I decided to play with Proxies.

So here is my simple little Fibonacci program.

var fib = function(x) {
  if (x === 0) return 0;
  if (x === 1) return 1;
  return fib(x-1) + fib(x-2)
}

Nice and simple. Now I create a memoization handler. This handler implements the memoization logic as part of a Proxy handler.

var memoizerMaker = function() {
  this.memoStore = {};
}
memoizerMaker.prototype.apply = function (target, thisValue, args) {
  var argsKey = args.toString();
  if (this.memoStore[argsKey] !== undefined) {
    return this.memoStore[argsKey];
  } else {
    let value = target.apply(thisValue,args);
    this.memoStore[argsKey] = value;
    return value;

  }

So this object has a "memo store" where it stores solutions. It also has a apply function that will be used by the Proxy. This apply function will be called instead of the target function. This apply will then call the target function and store the output.

Now to make my Fibonacci function use this memoization object.

fib = new Proxy(fib, new memoizerMaker());

Now let's say I am trying to compute the length of a Collatz sequence. It goes like this:

function computeCollatzLength(number) {
  if (number === 1) {
    return 1;
  } else if (number % 2 === 0) {
    return 1 + computeCollatzLength(number / 2);
  } else {
    return 1 + computeCollatzLength(number * 3 + 1);
  }
}

Now memoizing this isn't as useful as memoizing Fibonacci. But imagine that you call this function multiple times. The function (actually the Proxy) has an attached object that stores all the values. All you have to do is this:

computeCollatzLength = new Proxy(
    computeCollatzLength, 
    new memoizerMaker());

This is some very elegant stuff!


1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete