memoize functions in CFML

August 29, 2019
I try to bend the internet to my will.
Wizard 32 posts
Followers: 21 people
5

memoize functions in CFML

I try to bend the internet to my will.
Wizard 32 posts
Followers: 21 people
August 29, 2019

I’ve been working with React hooks recently and one of them allows you to memoize calculations. This is useful where you have a slow, or computationally expensive, function as given the same arguments you’ll get the cached result instead of recalculating each time. The same can be done in CFML code.

To demonstrate here’s simple function to simulate a slow operation

function slow( a, b ) {
  sleep( 1000 ); // pause for a second
  return a*b;
}

Calling this function with the same arguments multiple times will return the same result but take the same amount of time to run each time.

We can create a memoize function which will wrap around the slow function and if it’s been previously called with the same arguments, it’ll return the previously calculated response instantly without needing to run the code in the slow function.

Here’s what the memoize function could look like:

function memoize( fn ) {
  var results = {}; 
  return function() {
    var argsKey = serializeJSON( arguments );
    if ( !structKeyExists( results, argsKey )) {
      results[argsKey] = fn( argumentCollection=arguments );
    }
    return results[argsKey];
  };
}

To use it with the slow function you would do:

memoizeSlow = memoize(slow);
result = memoizeSlow(10,2);

Now if we call it for a second time, with the same arguments, we will get an instant response.

s = getTickCount();
r = memoizeSlow(10,2);
e = getTickCount();

writeDump({
  result: r, // 20
  ms: e-s // 1001
});

// call again with same args
s = getTickCount();
r = memoizeSlow(10,2);
e = getTickCount();

writeDump({
  result: r, // 20
  ms: e-s  // 0
});

// call with new args
s = getTickCount();
r = memoizeSlow(10,3);
e = getTickCount();

writeDump({
  result: r, // 30
  ms: e-s  // 1008
});

// call again with original args
s = getTickCount();
r = memoizeSlow(10,2);
e = getTickCount();

writeDump({
  result: r, // 20
  ms: e-s  // 0
});

As you can see, we are getting the expected results, but with a performance boost.

If you always wanted the slow function to work this way you could skip the intermediate step and just pass the function in directly like so:

memoizeSlow = memoize( function( a, b ) {
    sleep(1000);
    return a*b;
});

Here is some runnable code if you want to play around with it:
https://cffiddle.org/app/file?filepath=bb1d967e-2431-4d72-9977-c5bafd4df1ae/71ecd970-6f51-44a7-b1a2-137c9c7b6205/5193e14b-ae30-4724-aac8-cce15357004c.cfm

Comments (5)
2019-09-05 15:30:39
2019-09-05 15:30:39

Hi Charlie, thanks for the comments – some good clarification in there 🙂Yeah, this is essentially achieving lightweight caching within the request. As you mention, CFML has it’s own caching functions and I’m not suggesting using this to replace them, it’s just another tool in the toolbox.

Hi Jim, I’ve looked at underscore.cfc before but hadn’t previously noticed that it already has a memoize function in it. That one is more sophisticated as you can pass in the hashFunction as well so if anyone wants to try out this kind of thing in real-world code, then they should definitely check that out. Thanks for mentioning it 🙂

Like
2019-09-05 13:35:28
2019-09-05 13:35:28

underscore.cfc is good for stuff like this.   http://russplaysguitar.github.io/UnderscoreCF/

Like
(1)
>
Jim Priest
's comment
2019-09-05 16:27:32
2019-09-05 16:27:32
>
Jim Priest
's comment

Cool Jim. I see it adds a memoize function, which may be what you were focused on for this comment.

Otherwise, we should note that that library (adding lots of functional programming features to CFML) is from the CF10 timeframe, before CF added more and more such functional programming features itself in 11, 2016, and 2018. Indeed, it says it’s compatible with CF10 and Railo 4 (the only versions listed), so it may be that some aspects of the lib may fail on later versions. Just clarifying, for readers.

Like
2019-09-04 14:31:05
2019-09-04 14:31:05

Following on my previous comment, I wanted to share some more thoughts, again in case they may help others seeing this post and considering using it.

First, it should be noted as well that this form of lightweight caching lives only for the life of the request (at least as written here). Folks who run his cffiddle example will see this more clearly, as each call to the page shows the first and third method calls always taking about a second, even on page refreshes. It’s the subsequent calls with the same args IN THE SAME REQUEST which show the speed boost.

And it can indeed have value to do such saving of calls within a single request. I just want to help readers realize that it will not help for repeated requests OF that given template.

Someone may be tempted to assert that this could be addressed by storing into a shared scope (session, application, or server) the variable holding the saved results (literally “results” in his code above). And sure, one COULD, but then you have to think carefully about whether doing that would make sense.

More than that, and especially if you may consider saving the results ACROSS requests, it should be noted this sort of “lightweight, implicit caching” lacks various potentially useful features like limiting how many results should be saved, or implementing an eviction strategy, etc. At the point that could become an issue, you should really consider instead using the actual caching features, tags, and functions that CFML offers. 🙂

None of this is to complain about the blog post or the memoization concept. Just sharing some additional thoughts.

Like
2019-09-04 14:25:02
2019-09-04 14:25:02

That’s very interesting, John. I’d not heard of memoization before. I see it’s been around a long time (https://en.wikipedia.org/wiki/Memoization). And I see that it relies upon the concept of closures (https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/extending-coldfusion-pages-with-cfml-scripting/using-closures.html), which were introduced in CF10. Just adding these additional resources in case they may help others coming upon this and wishing to learn more.

Thanks for sharing it and the working examples. (I have some more thoughts I will share separately.)

Like
Add your comment