Default CFParam Expressions Are Always Executed In ColdFusion
Yesterday, my world was somewhat rocked when I discovered that default CFArgument expressions are executed only as-needed in the ColdFusion runtime. This was awesome news; and, it made me wonder if I've been wrong about other contexts that can employ default values. The one that immediately jumps to mind is the CFParam tag which can assign a default value to a variable reference if it is not already defined.
To test this, I set up a similar situation - I am employing a getDefaultValue() function in my CFParam tags; but, I am only requiring it (theoretically) for 2 out of 5 parameters:
<cfscript>
// I return a default value for use in an optional parameter.
numeric function getDefaultValue() {
return( ++defaultValue );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Since the defaultValue is pre-incremented above, we will be
// able to see how many times the getDefaultValue() method is
// actually invoked at runtime.
defaultValue = 0;
// Setup THREE out of FIVE optional parameters.
url.a = "exists-already";
url.b = "exists-already";
url.c = "exists-already";
// Now, param five values. Since we have the three above, only
// two of the five below should receive the default value.
param name = "url.a" type = "any" default = getDefaultValue();
param name = "url.b" type = "any" default = getDefaultValue();
param name = "url.c" type = "any" default = getDefaultValue();
param name = "url.d" type = "any" default = getDefaultValue();
param name = "url.e" type = "any" default = getDefaultValue();
// At this point, "defaultValue" will report the number of
// times the getDefaultValue() expression was executed.
writeOutput( "Script Result: " & defaultValue );
// Output the URL values at this point, so we can see which
// values were defined.
writeOutput( "<br />" );
writeOutput( "Url: #a#, #b#, #c#, #d#, #e#" );
</cfscript>
When I run this code, I get the following page output:
Script Result: 5
Url: exists-already, exists-already, exists-already, 4, 5
Here, we see a different behavior than we saw yesterday with the CFArgument tag. In the CFParam context, the default expression is executed for every CFParam statement; however, the generated value is only applied to the last 2 CFParam tags that do not have pre-defined variable references.
This is more in alignment with my long-standing notions about default-expression evaluation. Though, I have to admit, I much prefer the way it's handled in the CFArgument tag. As a final note, I should mention that this same behavior is exhibited in a Tag-context, but I didn't bother showing it.
Want to use code from this post? Check out the license.
Reader Comments
Dammit! I've got a blog article half-written on this very subject. How weird is that (in the sense of "coincidence"). Will have to come up with something else now. Damn you, Nadel. ;-)
--
Adam
@Adam,
Ha ha, sorry!
If you want something to look into, I just tried running this in both Adobe / Railo using CFLive.net. They have two very different outcomes. I don't know *anything* about the Railo engine, so I am not sure what that means. I know you poke around with Railo, so maybe that will make more sense to you.
Yeah, this is a fun one. It makes sense and is sooo frustrating at the same time.
It would be really cool if a "deferred" attribute could be added to cfparam so that it basically did an EVAL of the default value expression if the variable needed to be set, and did NOTHING if the variable existed already.
The difference on Railo has to do with scopes. I think Railo is implying a local scope on the variable in the function and default initializing it to 0. When you replace defaultValue with variables.defaultValue both ACF and Railo return the same results.
@Tom,
Ah, interesting. I am not sure how I feel about that. It seems to go against what other languages [that I use] do, ex. JavaScript.
@Ben,
I fully agree that the Railo functionality is wrong. CFML is supposed to look for variables in existing scopes before creating new ones. It can make the code hard to read, but that's the way it's work with ACF since as far as I can remember.
@Tom,
Yeah, true, it searches like 8 different scopes or something before creating them, from what I can remember.