The Elvis Operator vs. IsNull() In ColdFusion 11 Beta
In the beginning, ColdFusion gave us isDefined(). Then, structKeyExists(). And, as the concept of "null" started to become a first-class concept, ColdFusion delivered unto us isNull(). Now, with ColdFusion 11 Beta, we have the "elvis" operator - ?: - so called because it looks like a well-coiffed emoticon. In the past, we have seen differences between isDefined() and isNull(). As such, I thought it would be good to compare the new Elvis operator to the current isNull() function.
In concept, the Elvis operator is something of a short-hand for value parameterization. Meaning, it allows us to "param" a value if it is currently null. The syntax for the operator is:
variable = possible_value ?: default_value;
If the value in question exists, it is returned as the outcome of the expression (and assigned to the variable on the left). On the other hand, if the value in question does not exist, the default value is returned as the outcome of the expression (and assigned to the variable on the left).
In philosophy, the following examples are all roughly equivalent:
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
value = ( url.foo ?: "bar" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
value = ( isNull( url.foo ) ? "bar" : url.foo );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
if ( isNull( url.foo ) ) {
value = "bar";
} else {
value = url.foo;
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// NOTE: This one is a little different since it's actually
// changing the state of url.foo.
cfparam( name = "url.foo", default = "bar" );
value = url.foo;
</cfscript>
The concept is simple, but what about the implementation? I've tried to run the Elvis operator through its paces, comparing it to the existing isNull() operator. In the first set of examples, I've provided values that should be null.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing unscoped values.
elvis = ( theKing ?: "default" );
nully = ( isNull( theKing ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing scoped values.
elvis = ( url.theKing ?: "default" );
nully = ( isNull( url.theKing ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing bracket notation.
elvis = ( url[ "theKing" ] ?: "default" );
nully = ( isNull( url[ "theKing" ] ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
Element theKing is undefined in a Java object of type class coldfusion.filter.UrlScope.
This is a win for the new Elvis operator! It is able to consume undefined bracket-notation expressions. IsNull(), on the other hand, throws an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing deeply-nested values.
elvis = ( url.foo.bar ?: "default" );
nully = ( isNull( url.foo.bar ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing strict null values.
javaNull = javaCast( "null", "" );
elvis = ( javaNull ?: "default" );
nully = ( isNull( javaNull ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing strict null members.
request.strictNull = javaCast( "null", "" );
elvis = ( request.strictNull ?: "default" );
nully = ( isNull( request.strictNull ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing undefined array index.
values = [];
values[ 2 ] = 2;
elvis = ( values[ 1 ] ?: "default" );
nully = ( isNull( values[ 1 ] ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
This is another win for the new Elvis operator! It is able to consume undefined array elements. IsNull(), on the other hand, throws an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing out-of-bounds array index.
values = [];
elvis = ( values[ 2 ] ?: "default" );
nully = ( isNull( values[ 2 ] ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
The element at position 2 of dimension 1, of array variable "VALUES,"; cannot be found.
Another win for the new Elvis operator! It is able to consume out-of-bounds array elements. IsNull(), on the other hand, throws an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing out-of-bounds list index.
list = "a,b,c";
elvis = ( list.getAt( 5 ) ?: "default" );
nully = ( isNull( list.getAt( 5 ) ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
Invalid list index 5.
Another win for the new Elvis operator! It is able to consume out-of-bounds list items. IsNull(), on the other hand, throws an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing function results.
public any function getNothing() {
return;
}
elvis = ( getNothing() ?: "default" );
nully = ( isNull( getNothing() ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing undefined return values (native methods).
values = [];
noop = function() {};
elvis = ( values.each( noop ) ?: "default" );
nully = ( isNull( values.each( noop ) ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing function results as containers.
public any function getNullContainer() {
return;
}
elvis = ( getNullContainer().foo ?: "default" );
nully = ( isNull( getNullContainer().foo ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
The system has attempted to use an undefined value, which usually indicates a programming error, either in your code or some system code. Null Pointers are another name for undefined values.
This is another win for the Elvis operator. It is able to reference keys on undefined return values. IsNull(), on the other hand, throws an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing function results as containers.
public any function getNullContainer2() {
return;
}
elvis = ( getNullContainer2()[ "foo" ] ?: "default" );
nully = ( isNull( getNullContainer2()[ "foo" ] ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
The system has attempted to use an undefined value, which usually indicates a programming error, either in your code or some system code. Null Pointers are another name for undefined values.
Another win for the Elvis operator. Like the previous example, the Elvis operator is able to use bracket-notation to referenced undefined return values. IsNull() continues to throw an error.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing undefined arguments.
public any function testArguments( string theKing ) {
elvis = ( theKing ?: "default" );
nully = ( isNull( theKing ) ? "default" : "false-positive" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
}
testArguments();
</cfscript>
Gives us the expected:
Elvis: default
Nully: default
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing non-enabled sessions.
elvis = ( session ?: "default" );
nully = ( isNull( session ) ? "default" : session );
writeOutput( "Elvis: " & isStruct( elvis ) & "<br />" );
writeOutput( "Nully: " & isStruct( nully ) & "<br />" );
</cfscript>
Gives us the somewhat expected / unexpected:
Elvis: YES
Nully: NO
Here, we are trying to reference the Session scope in a page that does not have sessions enabled. If you were to try an CFDump the session scope, you would get:
The requested scope session has not been enabled.
Philosophically, that should make the scope null, which means that both of the above statements should report a non-struct "default" value. However, the Elvis operator seems to believe the session scope exists.
At first, you may think this is a win for the existing isNull() operator. But, you would be mislead. In fact, if we turn session-management on such that the session scope does exist, the outcome above does not change. Meaning, the Elvis operator continues to think that the session scope exists and the isNull() operator continues to think that the session scope does not exist.
In this case, both are a fail.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing custom tag's CALLER scope.
parent = {};
cf_tagfail();
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
... with the ColdFusion custom tag:
<cfscript>
caller.elvis = ( caller[ "parent.member" ] ?: "default" );
caller.nully = ( isNull( caller[ "parent.member" ] ) ? "default" : "false-positive" );
</cfscript>
Gives us the ColdFusion error:
Elvis: default
Element parent.member is undefined in a Java object of type class coldfusion.runtime.PageScope.
This is another win for the Elvis operator, which would work with undefined values in the Caller scope.
Those are all the "null" use-cases that I could think of last night. And, as you can see, the Elvis operator is actually a winning addition in a few of the use-cases.
Now, let's take a quick look at non-null use cases. Meaning, let's look at how the Elvis operator performs when the value in question already exists.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing "undefined" CGI members.
elvis = ( form ?: "false-negative" );
nully = ( isNull( form ) ? "false-negative" : form );
writeOutput( "Elvis: " & isStruct( elvis ) & "<br />" );
writeOutput( "Nully: " & isStruct( nully ) & "<br />" );
// NOTE: Long-standing issue.
// http://www.bennadel.com/blog/1773-IsNull-vs-IsDefined-For-ColdFusion-9-Scope-Detection.htm
</cfscript>
Gives us the somewhat expected:
Elvis: YES
Nully: NO
I say "somewhat expected" because it's a long-known issue that isNull() cannot properly identify scopes. I am not sure if this is a win for the Elvis operator, as I am too lazy to perform a request that eliminates the FORM scope. But, given the session-check above, it's entirely possible that neither of the approaches are working properly.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing "undefined" CGI members.
elvis = ( cgi.cf11 ?: "false-negative" );
nully = ( isNull( cgi.cf11 ) ? "false-negative" : "" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives the expected:
Elvis:
Nully:
Both operators correctly return an empty string for any undefined CGI value.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing new "member" functions.
value = "ColdFusion 11";
elvis = ( value.ucase() ?: "false-negative" );
nully = ( isNull( value.ucase() ) ? "false-negative" : "" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the unexpected:
Elvis: false-negative
Nully:
This is a fail for the Elvis operator. It didn't understand that the result of the native method was a non-null value.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing Java functions.
values = [ 1 ];
elvis = ( values.size() ?: "false-negative" );
nully = ( isNull( values.size() ) ? "false-negative" : "" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the unexpected:
Elvis: false-negative
Nully:
Another fail for the Elvis operator, having trouble with member function returns.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing Java functions.
request.getSomething = function() {
return( true );
};
elvis = ( request.getSomething() ?: "false-negative" );
nully = ( isNull( request.getSomething() ) ? "false-negative" : "" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the unexpected:
Elvis: false-negative
Nully:
Another fail for the Elvis operator. Clearly, the Elvis operator doesn't understand how to evaluate the non-null return value for any method, native or otherwise.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing first-class native functions.
elvis = ( ucase ?: "false-negative" );
nully = ( isNull( ucase ) ? "false-negative" : "" );
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
Gives us the unexpected:
Elvis: false-negative
Nully: false-negative
In this case, neither operators correctly identify the first-class native method.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing user defined functions
public any function noop() {
return;
}
elvis = ( noop ?: "false-negative" );
nully = ( isNull( noop ) ? "false-negative" : noop );
writeOutput( "Elvis: " & isCustomFunction( elvis ) & "<br />" );
writeOutput( "Nully: " & isCustomFunction( nully ) & "<br />" );
</cfscript>
Gives us the expected:
Elvis: YES
Nully: YES
At least they both get this one right; while neither operators could identify the first-class native method, they both can properly identify a first-class user-defined function.
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Testing custom tag's CALLER scope.
parent = {
member: "exists"
};
cf_tagpass();
writeOutput( "Elvis: " & elvis & "<br />" );
writeOutput( "Nully: " & nully & "<br />" );
</cfscript>
... with the ColdFusion custom tag:
<cfscript>
caller.elvis = ( caller[ "parent.member" ] ?: "false-negative" );
caller.nully = ( isNull( caller[ "parent.member" ] ) ? "false-negative" : caller[ "parent.member" ] );
</cfscript>
Gives us the expected:
Elvis: exists
Nully: exists
Both operators correctly work with an existing variable name in Caller scope.
Well, I'm running out of time so I'll wrap this up quick. The Elvis operator has a few little bugs, mostly with false-negatives. But, overall, it definitely operators in a wider range of scenarios when compared to its sibling function / operator, isNull().
NOTE: Sorry, I didn't really have time to proof-read this post. Damn you day job!
Want to use code from this post? Check out the license.
Reader Comments
Not 'elvis' but ternery
http://en.wikipedia.org/wiki/%3F:
@Michael,
It looks like when it's used with only two values, it's the Elvis operator (according to the link you sent):
See also Elvis operator when ?: is used as a null-coalescing binary operator with only two operands.
So, it looks like it's a specialized form of the ternary operator.
I realize this is a super-old thread at this point, and entirely possible that my question's already been answered. But 12,080 comments is a *lot* to weed through.
I also don't have a machine to play with CF 11 yet, to answer these questions myself.
So my questions are:
Example 1:
Carlist1 = arrayNew(1);
Carlist1[1] = 'Ferrari';
Carlist1[2] = '';
Thiscar = (Carlist1[2] :? 'none');
writeOutput('Second parking spot is taken by '&Thiscar&'.<br>');
Should return
Second parking spot is taken by none.
But if I now output Carlist1[2], will it also have obtained a value of 'none'?
Example 2:
Carlist2 = arrayNew(1);
Carlist2[1] = 'Ferrari';
Thiscar = (Carlist2[2]:?'none');
writeOutput('Second parking spot is taken by '&Thiscar&'.<br>');
Should return
Second parking spot is taken by none.
But if I now output Carlist2[2], will it now have been defined and obtain the value of 'none'?
Sorry. Let me clarify my questions.
Question one should be "will Carlist1[2] have obtained any value?".
Question two should be "will Carlist2[2] be defined and have a null value?".