Monday, 19 August 2013

Thinking about operators in CFML

I was messing around with some array-building code the other day - I don't even recall what it was for now - and I thought if I want to concatentate two arrays, why do I need to do this:

lowNumbers = ["tahi", "rua", "toru", "wha", "rima", "ono"];    // the ones I remember
higherNumbers = ["whitu", "waru", "iwa", "tekau"];            // the ones I always have to look up

numbers = lowNumbers;

writeDump([lowNumbers,higherNumbers, numbers]);

Why can't I just do this:

numbers = lowNumbers + higherNumbers;

Now I realise that as of ColdFusion 10 (and some version of Railo: not sure which), arrayAppend() also optionally acts as an arrayConcatenate() function, but I'm on CF9 so that's no help. However it got me thinking... isn't just using the + operator more sensible? I jumped onto to see if Ruby allows this: and it does. Good: a precedent as been set here.

So I think it makes a certain amount of sense to be able to use the + and += operators on both arrays and structs. + works between two objects, returning a third; += works on the first operand, updating it inline. This language progression is like how we used to have to do this:

st = structNew();
structInsert(st, key, value);

Then we could set a struct key/value with the [] operators:

st = structNew();
st.key = value;

Now we can just use the {} operator to create a struct using literal syntax:

st = {key=value};

So to concatenate arrays we used to need to do it by hand, then use a function, but now simply using an operator makes sense.

Further to this, how about the equality operators? There's no reason this shouldn't work:

if (someArray == someOtherArray){
    // do stuff

(This should do a value-level comparison, and for doing a reference level one, we'd need the usual === operator too).

The == operator should work on any object that implements an equals() method (or better: implements a Comparable interface).

This got me thinking, which other operators are out there which CFML could benefit from. Here's a few I found that seem pretty handy:

<=>CompareGroovyAnalogous to CFML's compare(). Should work for any object for which there's an appropriate compareTo() method (see Comparable).
==~Equals patternGroovyReturns true if the regex in the second operand matches the first operand, eg:

'a' ==~ '[a-z]'

would be true
?.Safe navigationGroovyThis is best demonstrated with an example:
a = { b ={ c = { d = 1 } } }; writeOutput(a?.b?.c?.d); // 1 writeOutput(a?.e?.c?.d); // null writeOutput(a.e.c.d); // ERROR
So rather than erroring because a.e doesn't exist, it gracefully returns null. Nice. This saves a lot of sequential structKeyExists() calls, or (grim) an isDefined() call.
??Null-coalescingC#This returns the first operand if its not null, otherwise the second. A use-case for this is for a kind of <cfparam>-lite sort of thing: defaulting variables which might (not) be set. Railo has implemented the Groovy version of this which is ?:, but I think that's a misleading construct as it looks identical to the already-extsing ternary operator, which is a different thing, and it's confusing to have two quite different operators which look the same. However I guess a precedent has been set with Railo, so perhaps deferring to ?: might be a reasonable thing to do.
..Inclusive RangeRubyThis is trickier as it creates a "range" which is a concept CFML doesn't have. However the concept is an interesting one.
The range 1..10 is basically a sequence of values from 1-10. If one was to convert it to an array, one would have [1,2,3,4,5,6,7,8,9,10].

Ranges can be used for sequences, eg:

for (i in range){
     // do stuff


Or in conditional statements, eg:
if (examScore in 50..100){

and that sort of thing.
...Exclusive RangeRubyAs above except the range is up to but not including the uppper boundary.

What do you think? Are there other operators in other languages that would be good for CFML to co-opt? I'd much rather CFML adopt things like this than more wizards that write JavaScript for us.