Friday 23 October 2015

ColdFusion 2016: it goes to show you should raise feature requests

G'day:
I'm still under NDA with the ColdFusion 2016 Prerelease Programme, so I need to be circumspect about what I say about CF2016, but once someone from Adobe has talked about something in public, I reckon I am allowed to at least refer back to that, and draw on public information to write some stuff.

Adobe showcased some ColdFusion 2016 features at CFCamp yesterday. It sounds like it was a good presentation, and was more dev-centric than has been the case in the past. Nice one, Rakshith.

A while back I raised this ticket on the bugbase: "?.: safe navigation operator". I gleaned this idea from trawling around other languages to see what they have on offer which might complement CFML. I wrote the results of this in my article "Thinking about operators in CFML".

Well I'm quite pleased that the safe-navigation operator has made its way into ColdFusion 2016.

Yay! <- that's me being pleased. It doesn't happen often, so remember what it looks like for future ref.

So what's this "safe navigation" thing?

It's an operator which takes two operands, and follows the following rules:
  1. if the first operand is null, it returns null;
  2. if the first operand is not null, it applies the second operand to it using the . operator.
Wah?

OK, say we have this:

// safeNavigationExample.cfm
function getMaoriNumberById(id){
    var numbers = ["tahi", "rua", "toru", "wha"];
    if (id <= numbers.len()){
        return numbers[id];
    }
}

fiveInMaoriInUpperCase = getMaoriNumberById(5).ucase();

We would get this:

Value must be initialized before use.

Its possible that a method called on a Java object created by CreateObject returned null.
The error occurred in
safeNavigationExample.cfm: line 10
8 : }
9 : 
10 : fiveInMaoriInUpperCase = getMaoriNumberById(5).ucase();
11 : </cfscript>

To counter this, we'd need to write this lot:

fiveInMaori = getMaoriNumberById(5);
if (!isNull(fiveInMaori)){
fiveInMaoriInUpperCase = fiveInMaori.ucase();
}else{
    fiveInMaoriInUpperCase = javaCast("null", "");
}

That's quite a mouthful. And this is only with one chained operation. What if it was this:

result = new SomeObject().method1().method2().method3().method4();

And at any point in that, the result of one of the methods might be null? We'd need this:

someObject = SomeObject();
resultOfMethod1 = someObject.method1();
if (!isNull(resultOfMethod1)){
    resultOfMethod2 = resultOfMethod1.method2();
    if (!isNull(resultOfMethod2)){
        resultOfMethod3 = resultOfMethod2.method3();
        if (!isNull(resultOfMethod3)){
            result = resultOfMethod3.method4();
        }
    }
}
if (!structKeyExists(variables, "result")){
    result = javaCast("null", "");
}

Yikes.

So this is where the safe navigation operator comes in:

result = new SomeObject().method1()?.method2()?.method3()?.method4();

Much better.



Now: I know that often having method which return either some value or a null can probably be better implemented, but this is not always the case: there are legitimate situations in which returning null is the correct way of handling the situation. And the calling code needs to handle that.

One thing to note here is that one might thing this would "work":

a = {
    b = {
        c = {
            d = "D"
        }
    }
};

e = a?.b?.c?.e;

This will error with something like:

Element E is undefined in a CFML structure referenced as part of an expression.

The error occurred in
notRhs.cfm: line 10
8 : };
9 : 
10 : e = a?.b?.c?.e; 
11 : </cfscript>

Why? Because remember the rules:
  1. if the first operand is null, it returns null;
  2. if the first operand is not null, it applies the second operand to it using the . operator.
The ?. operator checks the left operand for null. Not the right operand. I have been caught out by this. In its Groovy origins this makes perfect sense because Groovy works with Java objects, and any object that exists will definitely have prescribed accessible properties or methods. There's no question about that. The check is whether the object exists, not the property or method of the object. That does not need checking for.

However this raises an interesting point... CFML is not the same in this regard. I think my example above is completely legit in CFML... it just did not occur to me when I raised the initial ticket.

One solution to this is that perhaps CFML would better benefit from a right-sided equivalent to Groovy's operator, say: .?

EG:

e = variables.?a.?b.?c.?e;

In CFML, we always know the first left-hand operand will always exists, if we use a scope (well: almost always, but in a given context, one will be certain enough). And this variation would work as follows:
  1. if the first operand does not have an accessible element that is the second element, it returns null;
  2. otherwise it applies the second operand to the first using the . operator.
This is a bit avant-garde, and is certainly very close to established behaviour with ?. in Groovy to be initially confusing I think. But it solves a real world problem, and this is perhaps a more legitimate concern that following Groovy for purely dogmatic reasons.

One other thing which might occur to you is that as well as dot notation... CFML also has associative array notation, eg:

e = a["b"]["c"]["e"];

If this is possible, then to stay inkeeping with the dot-notation version of the safe navigation operator, then it stands to reason that this should also be supported:

e = a?["b"]?["c"]?["d"];

Or if we went the opposite route, checking the right-hand operand for nullness:

e = variables[?"a"][?"b"][?"c"][?"e"];

Does that open the door for array-index checking:

e = someArray[?5];

None of this was mentioned at CFCamp, so I'm not sure what conclusion we should draw about that?

Unfortunately there can be no public discussion on this (well: no acknowledgement of public discussion, anyhow... we're free to talk about whatever we like!) whilst ColdFusion 2016 is in pre-release (and I'm under NDA, so I'm further restricted). However the ticket is public (reminder: 3614459), so we as the public can discuss our concerns quite legitimately there. And Of course Adobe are free to feed-back whatever they like.

Thoughts?

--
Adam