Considering Polyfill Function Mechanics And Hoisting In ColdFusion
In my work, I use both Adobe ColdFusion and Lucee CFML. The functionality in these application severs overlap to a great degree. However, they each have built-in functions that are unique. The ones that always jump to mind for me are Lucee's dump()
and structValueArray()
functions. Now, dump()
is really just an alias for ColdFusion's writeDump()
function; but, creating a function alias, which I looked at earlier, is different than creating a polyfill. Unlike an alias, which is always applied, a polyfill is only meant to be applied in a context in which it is needed. And, implementing this in ColdFusion can be a little confusing.
In Adobe ColdFusion, there's no native, documented way to create a globally available function. So, whatever we do, there will be artifacts relating to the polyfill that show up in the calling code. In this exploration, we'll limit those artifacts to the CFInclude
tag. That is, we'll hide our polyfill implementation in an included template.
And to implement the polyfill, we must understand the mechanics of a function declaration. In ColdFusion, just as in JavaScript, there are two different ways to define a function:
- As a declaration.
- As an expression.
A declaration is what you use for most functions. It's the stand-alone method definitions that make up just about every ColdFusion component member method. An expression, on the other hand, is what you use when you pass a fat-arrow function into another operation (such as arrayMap()
).
Both function declarations and function expressions result in a Function object. However, these two Function objects have two different life-cycles. With a function expression, the function is only created when the expression is evaluated. With a function declaration, the function always exists and is automatically hoisted to the top of the current page context.
Function hoisting (and var
hoisting) is a magical feature of CFML and JavaScript and many other languages; and vastly improves the ergonomics of the developer experience. But, it creates a complexity for function polyfills.
To see what I mean, consider this ColdFusion code:
<cfscript> | |
if ( false ) { | |
private void function demo() { | |
// .... this function will be hoisted .... | |
} | |
} | |
writeDump( | |
label = "Demo function was hoisted!", | |
var = demo | |
); | |
</cfscript> |
If you were unfamiliar with the mechanics of hoisting, you might assume that this code throws a null pointer exception since we're referencing the demo
function that was defined inside an if
-block that never executed. However, when we run this ColdFusion code, we get the following output:

As you can see, even though our if
-block never executed, the demo()
function was still defined. This is because we defined it using a function declaration; and, function declarations are hoisted to the top of the page. As such, it doesn't matter if our if
-block runs, the compilation process still exposes the function.
In the context of a polyfill, this is problematic because it means than any function we try to declare conditionally will be created. As such, we can't simply check for the existence of a native function and then declare it:
<cfscript> | |
if ( ! getFunctionList().keyExists( "structValueArray" ) ) { | |
private array function structValueArray() { | |
// .... this function will be hoisted .... | |
} | |
} | |
</cfscript> |
This will run fine in Adobe ColdFusion, where there is no conflicting structValueArray()
function. But, if we try to run this in Lucee CFML, we get the following error:
Error: The name
structValueArray
is already used by a built in Function; Failed intest3.cfm:5
.
The goal of a polyfill is—generally—to run safely everywhere, but only modify the runtime where needed. As such, this approach doesn't make for a good polyfill.
To safely create a cross-platform polyfill, we can use a conflict-free function declaration; and then conditionally create an alias to said function as needed. In the following code, we'll declare user defined functions that end with Impl
(short of "implementation") so as to avoid conflict. Then, inside our assessment if
-block, we'll assign the Impl
version to the native name:
<cfscript> | |
// Polyfill the Lucee CFML function. | |
if ( ! getFunctionList().keyExists( "structValueArray" ) ) { | |
// The function definition for "structValueArrayImpl" is hoisted to the top of | |
// the page context during compilation and processing. Then, as the expressions | |
// are being evaluated here, in this if-block, we're assigning the hoisted | |
// function to a context-level variable with a new name (the polyfilled name). | |
structValueArray = structValueArrayImpl; | |
} | |
// Polyfill the Lucee CFML function. | |
if ( ! getFunctionList().keyExists( "dump" ) ) { | |
dump = dumpImpl; | |
} | |
// Use the two polyfilled functions in Adobe ColdFusion. | |
dump( | |
structValueArray([ | |
key1: "value1", | |
key2: "value2", | |
key3: "value3" | |
]) | |
); | |
// ------------------------------------------------------------------------------- // | |
// ------------------------------------------------------------------------------- // | |
/** | |
* A simplistic polyfill of the structValueArray() method. | |
*/ | |
private array function structValueArrayImpl( required struct input ) { | |
return structKeyArray( input ) | |
.map( ( key ) => input[ key ] ) | |
; | |
} | |
/** | |
* A simplistic polyfill of the dump() method. | |
*/ | |
private void function dumpImpl( required any var ) { | |
writeDump( var ); | |
} | |
</cfscript> |
At the bottom of the file, we're declaring two functions that end with the Impl
suffix. Both of these functions are hoisted to the top of the page context once the template is compiled. But, we're only re-assigning to a context-level variable as needed.
Now, if we run this code, it works in both Lucee CFML (using the native implementation) and in Adobe ColdFusion (using the polyfilled implementation):

By using function hoisting and conditional aliasing, we were able to successfully create a polyfill. But, this is a lot of rigamarole that we arrived at iteratively. Now that we understand function hoisting a bit more, however, we can create a simpler solution that defers the hoisting mechanics.
Consider a CFML template called polyfill.cfm
. Inside this CFML template, we're going to execute an immediately-invoking function expression (commonly pronounced, "Iffy"). This polyfill file will iterate over a list of polyfilled function names; and then includes each polyfill function, as needed, using CFInclude
. In this approach, each polyfill lives in its own file:
<cfscript> | |
(() => { | |
var nativeFunctions = getFunctionList(); | |
var polyfillFunctions = [ | |
"dump", | |
"structValueArray" | |
]; | |
for ( var methodName in polyfillFunctions ) { | |
if ( ! nativeFunctions.keyExists( methodName ) ) { | |
include "./#methodName#.cfm"; | |
} | |
} | |
})(); | |
</cfscript> |
Here, the "iffy" creates a container for the var
variables in order to prevent them from leaking into the calling context. But, the iffy doesn't prevent the function declarations inside the subsequent CFInclude
tags from being hoisted to the top of the page context. And, at the same time, it doesn't execute the included CFML templates unless they are needed. Which means that our polyfill functions can now use the native, non-conflicting names.
Here's the ./dump.cfm
template that declares the dump()
polyfill:
<cfscript> | |
/** | |
* A simplistic polyfill of the dump() method. | |
*/ | |
private void function dump( required any var ) { | |
writeDump( var ); | |
} | |
</cfscript> |
And here's the ./structValueArray.cfm
template that declares the structValueArray()
polyfill:
<cfscript> | |
/** | |
* A simplistic polyfill of the structValueArray() method. | |
*/ | |
private array function structValueArray( required struct input ) { | |
return structKeyArray( input ) | |
.map( ( key ) => input[ key ] ) | |
; | |
} | |
</cfscript> |
Since these CFML templates are only included if they don't conflict with native methods, we don't have to worry about the naming conflicts. However, since they are still declared function and not function expressions, once the CFML template is parsed, the functions are hoisted up, past the "iffy" container and exposed on the page context. Which means that we can use them in whichever template originally included the polyfill.cfm
file:
<cfscript> | |
include "./polyfill.cfm"; | |
// Use the two polyfilled functions in Adobe ColdFusion. | |
dump( | |
structValueArray([ | |
key3: "value3", | |
key4: "value4", | |
key5: "value5" | |
]) | |
); | |
</cfscript> |
Function and variable hoisting mechanics are just wonderful features of the language, aren't they?!
Now, to be fair, I've never actually used this approach to polyfill functions in ColdFusion. Mostly, I don't write for cross-platform runtimes. So, if there's a runtime that's missing a function that I want, I just write it. But, I think the approach is generally sound.
Want to use code from this post? Check out the license.
Reader Comments
@Ben
This is really clever and gives me a much better understanding of CF's function mechanics. I appreciate how you step through the solution by first building up the problem in a clear, uncluttered, and understandable way. It must take you considerable time to craft your posts.
Bonus is I always learn about hidden gems like
getFunctionList()
reading through your examples@Chris,
It's really fun to explore this stuff. And, I've been feel really creatively blocked lately (suffering from a lot of anxiety and depression the last few months); so, it's been nice to just get something written, even if it's not the most practical stuff. At the very least, it's fun to explore some areas of the language people might not thing about.
Also, to be fair, this might be the first time I've ever used
getFunctionList()
myself 🤣@Ben,
I'm really sorry to hear about your depression and anxiety. It can be so difficult to find your way out of that haze. I find having purpose helps me, which is one reason I'm so grateful to have a company to run. I find purpose in self employment...gets me out of bed and keeps me motivated. I'm sure your blog helps you in similar ways. Always nice to have something external to yourself to focus on in such times.
I get so much from what you share, practical or not. Be well my (online) friend.
@Chris,
Yeah, it's not awesome :D I wouldn't mind the mental parts of it so much except that I'm also having some physical symptoms (RSI pain in my arms) which I'm 99% sure of brought on by the stress of this mid-life crisis. I am sure it will all start to get better as I ease into this next chapter of my life and start to feel like I'm actually contributing something at work. But, right now, I feel like I've reset to zero. I'm trying to engage with the whole newness / learning aspect; but, it's been an uphill battle. I think just getting some more writing done will help me feel more normal.
I appreciate your support!
Hey Ben, I didn't know about getFunctionList() either. That will come in handy :)
Like Chris, I really appreciate how you put the time in to help others. I have personally gained from your gems of wisdom dozens of times.
And I'm certain there are many others that have as well.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️