Wednesday 4 November 2015

ColdFusion 2016: arrays now passed by reference

G'day:
There's not much to say about this one. ColdFusion is finally playing catch-up, and standardising its approach to passing complex objects.

Traditionally, ColdFusion has passed "simple values" (strings, numerics, dates, etc) by value, and complex objects (structs, queries, XML) by reference (provided one understands "pass by reference" with these caveats: "Complex data-types in CF, and how they're not copied by reference"). The one exception was arrays. Arrays were passed by value. Why? I don't bloody know.

Right from the outset Railo decided that was bloody daft and has never done this, and this has been inherited by Lucee.

Now ColdFusion has caught up, even if I think the specific implementation is lacking.

There is a new setting for Application.cfc, this.passArrayByReference, which one can set to true (the default is false). If one sets that, then the behavour of passed arrays is changed.

Let's have a look at some code running on ColdFusion 11:

function arrayToUpperCase(array){
    for (var i=1; i <= array.len(); i++){
        array[i] = array[i].ucase();
    }
    return array;
}

rainbow    = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Poropango","Papura"];

rainbowInUpperCase = arrayToUpperCase(rainbow);

writeDump(var=rainbow, label="rainbow", format="text");
writeDump(var=rainbowInUpperCase, label="rainbowInUpperCase", format="text");

This simple code has a function which takes an array, upper-cases each element and returns it. Afterwards, we dump both the original and returned arrays:

rainbow - array

1) Whero 
2) Karaka 
3) Kowhai 
4) Kakariki 
5) Kikorangi 
6) Poropango 
7) Papura 
rainbowInUpperCase - array

1) WHERO 
2) KARAKA 
3) KOWHAI 
4) KAKARIKI 
5) KIKORANGI 
6) POROPANGO 
7) PAPURA 

As you can see, when we modify the passed-in array, it does not impact the original array. Constrast this with the same operation with a struct:

function structToUpperCase(struct){
    for (var key in struct){
        struct[key] = struct[key].ucase();
    }
    return struct;
}

rainbow    = {red="Whero", orange="Karaka", yellow="Kowhai", green="Kakariki", blue="Kikorangi", indigo="Poropango", purple="Papura"};

rainbowInUpperCase = structToUpperCase(rainbow);

writeDump(var=rainbow, label="rainbow", format="text");
writeDump(var=rainbowInUpperCase, label="rainbowInUpperCase", format="text");

rainbow - struct

BLUE: KIKORANGI
GREEN: KAKARIKI
INDIGO: POROPANGO
ORANGE: KARAKA
PURPLE: PAPURA
RED: WHERO
YELLOW: KOWHAI
rainbowInUpperCase - struct

BLUE: KIKORANGI
GREEN: KAKARIKI
INDIGO: POROPANGO
ORANGE: KARAKA
PURPLE: PAPURA
RED: WHERO
YELLOW: KOWHAI

As you can see, because structs are passed by references, the argument in the function references the same struct as in the calling code, so changes to the argument are reflected in the original.



Now in ColdFusion 2016, let's switch that setting on, and run the array version again:

// Application.cfc
component {
    this.passArrayByReference = true;
}

(the other file is the same as before)

D:\src\CF2016\arrays\passByRef\true>cf example.cfm
rainbow - array

1) WHERO
2) KARAKA
3) KOWHAI
4) KAKARIKI
5) KIKORANGI
6) POROPANGO
7) PAPURA

rainbowInUpperCase - array

1) WHERO
2) KARAKA
3) KOWHAI
4) KAKARIKI
5) KIKORANGI
6) POROPANGO
7) PAPURA

D:\src\CF2016\arrays\passByRef\true>

There you have it: being passed by reference. Cool.

Why's this a good thing? It just saves resources. Often when passing an array around, it's not being used for an operation which changes it, so there's no need to take a copy of it: the original one is fine. Secondly if one does want to change the array, then simply doing it inline on the original is more expedient than copying the original and changing that. And if one wants to preserve the original array, one can still duplicate() it.

So what do I not like about this implementation? Two things.

Firstly: it's off by default. It should be on by default, and if people have legacy code that relies on the old behaviour and they're unwilling to update their code: they can change the setting.

Secondly I hate these Application.cfc settings. Mostly because they end up being unusable. Having an application setting implies that either setting is the "currently supported" behaviour. However third-party developers (of frameworks and libraries and the like) are pretty much forced to use the default setting anyhow. As this is at application level then the application itself needs to use this default setting too, which invalidates having the setting in the first place.

What I think Adobe should do is to take the position "as of ColdFusion 2016, arrays are passed by reference". And that's how code runs. And then - buried in some server config file - have a "legacy behaviour" section, in which this behaviour can be set back to behave in legacy mode. This still gives the same options, but more emphatically says "the new behaviour is how things should be done".

Other than that, this functionality is a welcome change.

Oh, btw... if you're writing an array-upper-casing function; do not do it like that. Just do it like this:

rainbowInUpperCase = rainbow.map(function(v){
    return v.ucase();
});

Avoid generic loops for dealing with arrays (or other collections) where possible. Make your code descriptive. I had to do it the way I had above because map() specifically returns a new array anyhow, so is no good for demonstrating this feature.

Righto.

--
Adam