Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandeep Paliwal and Kiran Sakhare and Rupesh Kumar and Jayesh Viradiya
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandeep Paliwal , Kiran Sakhare , Rupesh Kumar@rukumar ) , and Jayesh Viradiya

CachedWithin Function Memoization Can Be Applied To Closures In Lucee 5.3.2.77

By Ben Nadel on
Tags: ColdFusion

Over the past couple of weeks, I've written a few posts about the cachedWithin Function memoization feature in Lucee 5.3.2.77. I've looked at how it compares complex inputs by value; and, at how it returns complex outputs by value. But, all of my explorations to date have been on a "proper" Functions. This morning, I wanted to quickly demonstrate that the cachedWithin Function memoization feature can also be applied to ColdFusion Closures in Lucee 5.3.2.77.

In order to add caching to a Function in Lucee CFML, all we have to do is add the cachedWithin directive to the Function declaration. This applies to ColdFusion Closures as well. Which means, we can create a caching version of any User Defined Function (UDF) by simply wrapping it inside of a Closure that contains a cachedWithin directive.

ASIDE: This only pertains to user defined functions (UDF) since native ColdFusion functions, like ucase, cannot be passed-around like first-class citizens. Built-in functions are a fundamentally different "Type" of object.

To see this in action, I've created a UDF that returns a random integer. Then, I pass that UDF to a memoization function that wraps it in a cachedWithin closure:

<cfscript>

	/**
	* I return a random integer between 1 and the given max value (inclusive).
	* 
	* @maxValue I am the optional max value of the range.
	*/
	public numeric function getRandomValue( numeric maxValue = 10 ) {

		return( randRange( 1, maxValue ) );

	}


	/**
	* I create a version of the given callback that uses cached responses for the
	* duration of the request.
	* 
	* @callback I am the Function being memoized.
	*/
	public any function memoizeForRequest( required any callback ) {

		// NOTE: We are applying the CachedWithin directive to the Closure definition.
		// This creates a caching proxy for the underlying callback.
		// --
		// NOTE: When adding meta-data for a Closure, you cannot use the Fat-Arrow
		// notation - you have to use the standard "function" syntax.
		return(
			function() cachedWithin = "request" {

				return( callback( argumentCollection = arguments ) );

			}
		);

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Test the base, non-caching function.
	echo( "<h2> Using Base Function </h2>" );
	echo( "#getRandomValue()# <br />" );
	echo( "#getRandomValue()# <br />" );
	echo( "#getRandomValue()# <br />" );
	echo( "#getRandomValue()# <br />" );
	echo( "#getRandomValue()# <br />" );
	echo( "<hr />" );

	// Now, let's wrap the base function in a caching proxy.
	cahedRandomValue = memoizeForRequest( getRandomValue );

	// Because this is the caching version, the following calls should all results in the
	// same output (from using the same input).
	echo( "<h2> Using Caching Closure </h2>" );
	echo( "#cahedRandomValue( 50 )# <br />" );
	echo( "#cahedRandomValue( 50 )# <br />" );
	echo( "#cahedRandomValue( 50 )# <br />" );
	echo( "#cahedRandomValue( 50 )# <br />" );
	echo( "#cahedRandomValue( 50 )# <br />" );

	// And, one final test to demonstrate that cachedWithin is based on inputs.
	echo( "#cahedRandomValue( 100 )# <br />" );

</cfscript>

As you can see, we're passing the getRandomValue() function into the memoizeForRequest() Function, which returns a caching-proxy to the original Function. Subsequent calls to this caching proxy should all result in the same output, assuming that the calls use the same inputs (arguments). And, when we run this CFML code, we get the following output:

The cachedWithin direcitve applied to a Closure caches the results of the underlying Function call in Lucee 5.3.2.77.

As you can see, when the cachedWithin Closure is invoked with no arguments, the same "random" value is returned 5-times in a row. This is because the first call to the proxy Closure cached the results of the underlying call to getRandomValue(); and then, each subsequent invocation of the Closure returned the cached value.

Of course, once we change the inputs to the caching proxy, the cachedWithin directive makes another call to the underlying getRandomValue() function, caching the new result.

Off the top of my head, I don't have a great use-case for this yet. But, it's good to know that the cachedWithin Function memoization feature of Lucee CFML 5.3.2.77 can be applied to ColdFusion Closures as well as normal User Defined Functions (UDF). This means that we can easily create on-the-fly caching for any Function call without having to explicitly manage a in-memory cache.



Reader Comments

I am really loving your exploration of these topics...and am following along. Super appreciate it. Also, really appreciate that you've begun providing video overviews again, making it much more likely that I follow along. And I enjoy hearing you talk through your thought process as well. ???? (<--prayer hands)

Reply to this Comment

@Chris,

Awesome my man, I am glad you are enjoying. It's fun to find all these little use-cases. And, I'm also happy to make the videos. I've historically been lazy about making them for server-side stuff. But, I do like the ability to paint a different picture - give it some different coloring and some more back-story. Definitely a value-add.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.