Skip to main content
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jeff Kunkel
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jeff Kunkel ( @Nerdtastic91686 )

Fundamental Differences In Elvis Operator Between Adobe ColdFusion And Lucee CFML

By on
Tags:

At work, we use Lucee CFML; but, on my blog, I use Adobe ColdFusion. I enjoy having my feet in both camps because it forces me to have a more robust mental model of each language - seeing what is and is not the same. And, for the most part, things line up well. But, this morning I stumbled upon Charlie Cochran's post on breaking changes in the Elvis operator in Adobe ColdFusion (ACF) which gave me pause. I panicked that I might be introducing subtle bugs into my applications. But, since he was talking about ACF, and I use Lucee CFML at work, I wanted to quickly test the two engines. And, oh chickens, they are very different!

To start, I'm a huge fan of the Elvis Operator ?: in ColdFusion. I love that it can be chained multiple times in a single expression and that it plays very nicely with the Safe Navigation operator. And, when trying to consume Golang's omitempty nonsense, it's really saved my bacon!

But, this was all in Lucee CFML, at work. And, it was all under the assumption that the "Elvis Operator" was really the same thing as the "Null Coalescing" operator. Which, apparently is not a great assumption.

In fact, Adam Cameron wrote about this divergence back in 2015. Heck, I even commented on that post in 2019; but, apparently I never committed it to my mental model.

Looping back to Charlie Cochran's post, the breaking change in Adobe ColdFusion is that the Elvis Operator no longer works as a Null Coalescing operator (as of CF2016). It now only acts as a short-hand for the ternary operator. Lucee CFML, on the other hand, seems to continue to treat this as a Null Coalescing operator. Let's look at the difference in the way the two engines handle Falsey Values:

<cfscript>

	echoVersion();

	// NOTE: I'm using echoArg() here because in Lucee, the left operand of the Elvis
	// operator has to be a Variable or a Function call - it cannot be a static value.
	echoValue( echoArg( 0 ) ?: "Ignored zero." );
	echoValue( echoArg( "0" ) ?: "Ignored string-Zero." );
	echoValue( echoArg( false ) ?: "Ignored false." );
	echoValue( echoArg( "false" ) ?: "Ignored string-False." );
	echoValue( echoArg( "no" ) ?: "Ignored string-No." );
	echoValue( echoArg( "" ) ?: "Ignored empty string." );

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

	// I output the current ColdFusion product version.
	public void function echoVersion() {

		var version = ( server.keyExists( "lucee" ) )
			? "Lucee CFML #server.lucee.version#"
			: "Adobe ColdFusion #server.coldfusion.productVersion#"
		;

		writeOutput( version & "<br /><br />" );

	}


	// I output the given value on its own line.
	public void function echoValue( required any value ) {

		writeOutput( value & "<br />" );

	}


	// I return the given argument.
	public any function echoArg( required any value ) {

		return( value );

	}

</cfscript>

As you can see, none of the values that I am testing are Null or Undefined. But, they do represent Falsey values and Falsey-esque values (ie, string versions of Falsey values). Now, when we run this in Adobe ColdFusion 2021 and Lucee CFML 5.3.8, we get the following output:

Adobe ColdFusion seen treating the Elvis operator as a short-hand for the ternary operator while Lucee CFML is seen treating the Elvis operator as a null coalescing operator.

Wow, that's a striking difference! This must be what Brad Wood and Luis Majano are talking about when they say that the Elvis operator is basically unusable in frameworks like ColdBox (which have to be cross-engine compatible) due to the number of issues that it introduces.

Adobe ColdFusion is basically treating this:

( a ?: b )

... as a short-hand for this ternary operation:

( a ? a : b )

And, Lucee CFML is basically treating it as a short-hand for this ternary operation:

( isNull( a ) ? b : a )

ASIDE: I am over-simplifying these short-hands - the conditional checks are actually a bit more complicated than this and have to take undefined and complex values into account. But, this is trying to underscore the fundamental differences, not the behaviors that the two engines have in common.

These are fundamentally different behaviors. And, personally, I prefer Lucee CFML's interpretation because Null Coalescing is really the operator that ColdFusion needs. If ColdFusion had full support for "Truthy values", like JavaScript does, then we might be having a different conversation. But, until that happens, Null Coalescing is the real value-add - not just a short-hand for the ternary operator.

At least now I have a better mental model for the Elvis operator in ColdFusion. Hopefully this time I won't forget.

Want to use code from this post? Check out the license.

Reader Comments

15,674 Comments

Even though I prefer Lucee CFML's interpretation of the operator, I should mention that Adam Cameron opened a bug report for this in Lucee several years back:

https://luceeserver.atlassian.net/browse/LDEV-468

If ColdFusion had two different operators, say ?: and ?? (JavaScript null coalescing operator), then I'd be OK with changing it. But, while we only have one operator, I would prefer for it to be of the null coalescing flavor.

15,674 Comments

Also, I should state that the Adobe ColdFusion documentation does document this as the behavior:

The support has been provided in ColdFusion for the Elvis operator (?:). The Elvis operator is primarily used to assign the 'right default' for a variable or an expression.

It returns the resultant value of its left operand's expression, if that resultant value is defined and not null and not a false value. Otherwise, it returns the resultant value of its right operand's expression (the default value).

... "and not a false value". That said, going back to Charlie Cochran's post, this apparently was not always the case in Adobe ColdFusion pre-2016.

15 Comments

Yep, I love Elvis and use it all the time in Lucee, but in Adobe it's a mash up abomination that has rendered it unusable. The current behavior leaves it completely unreliable because you don't know what it's going to do! Imagine a simple method in a library such as

function validateConfig( setting1, setting2 ) {
  this.setting1 = arguments.setting1 ?: 'default';
  this.setting2 = arguments.setting2 ?: 'default';
}

As soon as one of those settings contains false, boom, your elvis falls on its face and returns the default value. What a disappointment! There is no reliable behavior here because Adobe just kept bending in the wind to all the people who didn't really understand language design and how Elvis needed to work in CFML.

What many people don't understand is that a prerequisite for being able to treat any variable as truthy in a language requires that language to have a system in place that provides rules for how to make those determinations. CFML has never had such a system (even though I've proposed one in both the Lucee and Adobe ticket trackers) outside of some special string behaviors (yes, no, etc). Therefore, any attempt to create an operator in CFML that treats any variable as truthy isn't going to work. Look at languages like Kotlin, and they also treat elvis as a null coalescing operator- which is the only actually useful behavior.

This all goes back to people who really had no idea how elvis worked in other languages ike JS or Groovy and had no idea how those languages treated truthy and falsey values who wanted to shoe-horn something similar into CFML. So they kept yelling at Adobe telling them they "did it wrong" until Adobe just gave up and left it as an awkward mashup of both behaviors! Adobe has PTSD and basically refuses to touch elvis again, so it will probably never get fixed :/

https://tracker.adobe.com/#/view/CF-4198933

Adobe thinks that if they just document the current behavior that will be fine, but it doesn't change the fact that it's unusable now for any sort of null checks on an incoming variable that may happen to contain false.

205 Comments

this article and subsequent discussion via comments were super interesting. I had never really thought about this deeply, but whenever I consider reaching for the elvis operator, I have always (in every single case) gone with the ternary instead. Now I know why. Thanks 🙏

15,674 Comments

@Brad,

The one redeeming feature of the Elvis operator in Adobe ColdFusion is that it at least works with Null values to do null coalescing. So, to your point, if you have to write code that accepts an open-ended type of data, then it's probably not safe because it will blow up with Falsey values. But, if you have total control over the code, where you know - for sake of discussion - that you'll only ever have Null or Struct, then at least you can use it there.

But yeah ... disappointing.

@Chris,

Yeah, at least the ternary operator makes it crystal clear what is going.

15 Comments

that you'll only ever have Null or Struct

Correct, if you know for sure the variable would be a complex value (struct, array, object, etc) then the behavior could be depended on. It's for simple value where the dragons lie. Unfortunately, given the complete lack of precedence and widespread understanding of these subtleties, I wouldn't be likely to use elvis at all on Adobe just given the amount of ambiguity it introduces.

1 Comments

Thanks, very good to know these differences. I find Elvis very convenient to clean up code that has potentially uninitialized variables like in cfexecute's catch block:

echo ("stderr: #isDefined('err')?err:'-'#)

into

echo ("stderr: #err?:'-'#")

I'm currently only using Lucee where this works logically but with recent Adobe versions "err" would display "-" if the variable had "0" etc falsey value.

15,674 Comments

@Turo,

That's a great use of it! And, as much as the Adobe ColdFusion approach is sub-optimal, there are still many cases where the "known bad values" still play very nicely with the Falsey logic. And, of course, anytime you expect the value to be a complex value (think Struct, Array, Query), both engines should work exactly the same.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel