Thursday, 30 July 2015

CFML: "Elvis" operator and null coalescing operators are two different things

G'day:
I've probably at least touched on this once before, but I'll do it again anyhow.

ColdFusion and Lucee (and before than Railo) have both got an operator ?: this is the binary equivalent of the ternary operator ?:. EG:

result = firstOperand ?: secondOperand; // binary
result = firstOperand ? secondOperand : thirdOperand; // ternary

The ternary version predates the binary one, so let's start with that. It works as follows:

result = booleanExpression ? valueToUseIfTrue : valueToUseIfFalse;

EG:

result = isInteger(17) ? "it's an integer" : "no it isn't"; // "it's an integer"
result = isInteger("nineteen") ? "it's an integer" : "no it isn't"; // "no it isn't"

The key thing is that the first operand needs to evaluate to a boolean. It's a shorthand for if/else.

Now I'm going to dip out of CFMLland for a moment, and back into the real world.



The binary version is the same, except it simply omits the true case:


result = booleanExpression ?: valueToUseIfFalse;

EG:


result = isInteger(17) ?: "no it isn't"; // true
result = isInteger("nineteen") ?: "no it isn't"; // "no it isn't"

This has got nothing to do with nulls.

Back in CFMLland now. Except in CFML. Because both Adobe and Railo cocked it up, because they didn't understand what they were doing.

There is a different concept called a null-coalescing operator. I first encountered this in C#, but it's around in other languages as well: SQL and PHP 7 being ones I have to hand.

Here is null-coalescing at work:

<?php
// nullCoalescing.php

echo "Null coalescing when using false: ";
echo false ?? "default";
echo "\r\n";


echo "Null coalescing when using null: ";
echo null ?? "default";
echo "\r\n";


This outputs:

Null coalescing when using false:
Null coalescing when using null: default


And on SQL:

-- nullCoalescing.sql

DECLARE @result VARCHAR(10)

SET @result = COALESCE(null, 'default')
SELECT  @result

SET @result = COALESCE(0, 'default')
SELECT  @result


This returns "default" and 0 respectively.

This is because "false" is not the same as "null". They are two different things (note that PHP is outputing its false value there, which converts to an empty string when used as a string).

On the other hand, let's look at how the Elvis operator works:

<?php
// elvis.php

echo "Elvis when using false: ";
echo false ?: "default";
echo "\r\n";


echo "Elvis when using null: ";
echo null ?: "default";
echo "\r\n";

// elvis.groovy

print "Elvis when using false: "
print false ?: "default";
print "\r\n";

print "Elvis when using null: ";
print null ?: "default";
print "\r\n";

Both the PHP version and the Groovy version do this:

Elvis when using false: default
Elvis when using null: default

Because the Elvis operator busies itself with booleans. In this case, and in both languages, null is not true.

Now the CFML version:

// elvis.cfm

writeOutput("Elvis when using false: ");
testVar  = false;
writeOutput(testVar ?: "default");
writeOutput("<br>");

function nuller(){}

writeOutput("Elvis when using null: ");
writeOutput(nuller() ?: "default");
writeOutput("<br>");


Result:
Elvis when using false: false
Elvis when using null: default


Which is... wrong.

I'm going to raise a bugs with both Adobe (4028653) and Lucee (LDEV-468) once I press "send" on this, and try to get this sorted out.

Hopefully that clarifies things with my readers who have been asking about this.

--
Adam