The CachedWithin Function Memoization Feature Appears To Compare Complex Objects By Value In Lucee 5.3.2.77
Earlier today, I demonstrated that the cachedWithin
Function memoization feature caches the output buffer in Lucee 5.3. While this was an unexpected behavior, I think it makes perfect sense. And, as a quick follow-up post, I wanted to share yet another cachedWithin
behavior that wasn't immediately obvious to me: Complex objects, like Structs, Arrays, and Queries, appear to be compared "by value" - not "by reference" - when it comes to the "unique arguments" portion of the Function memoization control flow in Lucee 5.3.2.77.
Coming from the world of JavaScript, where immutable data structures are currently en vogue, the concept of "uniqueness" applies to simple values and object references. Meaning, two separate object references are different even if they aggregate the same values. As such, I just assumed that Lucee's cachedWithin
memoization worked the same way.
After some testing, however, it seems that Lucee CFML will actually calculate uniqueness by looking at the contents of complex data structures. That is, for example, if two different Arrays contain the same values, Lucee's cachedWithin
feature sees them as the same "input".
To demonstrate this, I created a collection of duplicate object declarations. Then, I looped over that collection and passed the individual values into a cachedWithin
Function which, in turn, incremented a counter. If the counter incremented, it meant that the inputs were "new" and the Function was executed; if the counter remained the same, it meant that the inputs were "known" and a cached result was returned:
<cfscript>
counter = 0;
// I test the caching of Function "outcome" in Lucee. Each execution of the non-
// cached version will increment the counter above.
// --
// NOTE: By using cachedWithin="request", the outcome of this Function will be
// memoized based on its arguments.
public any function testCaching() cachedWithin = "request" {
return( ++counter );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// This collection contains PAIRS of DUPLICATE COMPLEX VALUES (ie, different
// REFERENCES that have the same underlying structure).
complexValues = [
[],
[],
{},
{},
[{}],
[{}],
[[]],
[[]],
[""],
[""],
[[],{}],
[{},[]],
{a:1},
{a:1},
{a:2},
{a:2},
{a:3,b:{}},
{a:3,b:{}},
{a:3,b:[]},
{a:3,b:[]},
{a:{b:1,c:{d:[1,2,3,4,{e:5}]}}},
{a:{b:1,c:{d:[1,2,3,4,{e:5}]}}},
deserializeJson( '{"a":{"b":1,"c":{"d":[1,2,3,4,{"e":5}]}}}' ),
queryNew( "id,name", "varchar,varchar", [["1","Kim"]] ),
queryNew( "id,name", "varchar,varchar", [["1","Kim"]] ),
new Simple( "hello" ),
new Simple2( "hello" )
];
for ( value in complexValues ) {
echo( "<p>#testCaching( value )# ... #serializeJson( value )#</p>" );
}
</cfscript>
As you can see, every single one of the items in this collection is a unique reference in memory. However, when we run this Lucee ColdFusion code, we can see that the cachedWithin
memoization feature comes into play:
When we look at these results, it becomes clear that "unique object reference" does not drive the uniqueness of arguments when it comes to the cachedWithin
memoization feature in Lucee CFML. Even two ColdFusion Component (CFC) instances are considered the "same input" if they aggregate the same public properties.
Coming from the JavaScript world, this value-level evaluation of inputs was not obvious to me. As such, I am assume that it won't be obvious to others. And, that other may find this post helpful.
Want to use code from this post? Check out the license.
Reader Comments
Super interesting that
[]
would be the same as{}
in terms of memoization. I can't think of a reason why this would matter, but feels wrong (to me). Does it make sense to you?My first thought was that Lucee must be memoizing based on the serialized response, but that's clearly not true. ??
Also...thanks for bringing back the videos!
FYI-the
??
in my last comment was the (thinking-emoji). I've been wondering how difficult it would be to create a map that would replace extended characters with their text-based equivalents rather than generalize them all with??
. Have you ever considered that?@Chris,
Yeah, the
{}
/[]
one is strange. I wonder if it's using some sort of Java hash-code under the hood and these evaluate to the same hash-code? To be honest, I don't really know much about Java.Re
??
, I think the problem is that my blog-code is so old, I never configured the database to supportutf8mb4
characters (which include the set of unicode that pertains to most emoji). That should be something I can just update in the database .... will put that on my backlog of site-ideas.@All,
A quick follow-up to this post, it appears that the return value of a
cachedWithin
function is also returned by-value, not by-reference:www.bennadel.com/blog/3674-function-results-are-returned-by-value-when-using-cachedwithin-in-lucee-5-3-2-77.htm
This pertains even to complex objects like Arrays, Structs, and ColdFusion components, that would typically be passed-around by-reference.