Thursday, 23 August 2012

Arrays in CFML (part 3)

G'day:
This article finishes up the coverage of array functions in CFML (there's more to discuss than just the functions: I'll get to that stuff in yet another article). Most of the array functions were covered in an earlier article, and I'm just finishing of the rest of them here. The functions here haven't been put in a separate article for a good reason: it's just that I'm daft and forgot to put then in the earlier article that should have included them. I dunno how I missed them of the list initially, as I based my "todo list" on the list of functions on the "Array Functions" page from the CF docs. Like I said: I'm just daft. Anyway... here they are.

Oh: just to put this in context, this is part 3 of a series of articles documenting how arrays are implemented / work / can be used in CFML. I'm undertaking this because I don't think there are is any good, thorough documentation on the topic out there.

Note (and thanks to Russ for reminding me to do this. that for the purposes of running this test code, I was using the following CFML engines:

Platform Version Source
ColdFusion 10,0,0,282462 server.coldfusion.productVersion
Railo 3.3.4.003 server.railo.version
OpenBD 2.0.2 server.bluedragon.version

If I vary from these versions, I will note it inline.

arrayContains() / arrayContainsNoCase() / arrayFind() / arrayFindNoCase() / arrayFindAll() / arrayFindAllNoCase()


I'm grouping all of these together because they're basically the same function. I honestly don't see why Adobe wasted everyone's time creating five functions here, cluttering the language even more than it is already. All they needed is this one function:

Integer function arraySearch(required Array arrayToSearch, required any objectToFind, optional String scope (ONE|all), optional Boolean caseSensitive TRUE)

That covers all the bases they need in a logical, predictable way. Also it would be best if they returned array was one of references to the elements in the array, not simply their index. Or maybe each element ought to be a struct returning the index and the element reference. Dunno. But what I do know is that only one function is necessary here.

When I see five functions performing minor variations of the same theme, all returning different things, I'm reminded of PHP. And no-one likes being reminded of that.

I'd advocate deprecating all of these in favour of one unified function. Thoughts?

Anyway, I'm supposed to be documenting what CFML does support, not what I think it ought to.

These functions all have a look-see in an array, and tries to find an object within it, returning their findings variously as a boolean (arrayContains()), integer (arrayFind() and arrayFindNoCase()), or array (arrayFindAll() & arrayFindAllNoCase()). They will look for not only simple values, but complex objects such as arrays, structs and CFC instances. They all claim to not support seeking COM or CORBA objects: no loss there. It did not occur to me to test with Java objects or XML objects (I might look into this). They all do what it says on the tin, except for all of them having bugs. The example code for all if these is very similar, so I'll post only the code for arrayContains():

data.struct    = {one="tahi"};
data.array    = ["rua", "toru"];
data.simple    = "wha";
data.object    = new Number("rima");

data.test = [
    data.struct,
    data.array,
    data.simple,
    data.object
];
writeDump(var=data.test, label="Baseline data");

// test just a simple value
writeOutput("simple: #arrayContains(data.test, "wha")#<br />");

// simple value but different case
writeOutput("simpleDifferentCase: #arrayContains(data.test, "WHA")#<br />");


// one each of an array, struct, object, using the actual value from the array itself
writeOutput("struct: #arrayContains(data.test, data.test[1])#<br />");
writeOutput("array: #arrayContains(data.test, data.test[2])#<br />");
writeOutput("object: #arrayContains(data.test, data.test[4])#<br />");


// test copy by assignment
elementToFind = data.array;
writeOutput("assignment: #arrayContains(data.test, elementToFind)#<br />");


// test shallow copy
elementToFind = structCopy(data.struct);
writeOutput("shallow: #arrayContains(data.test, elementToFind)#<br />");


// test duplicate atruct
elementToFind = duplicate(data.struct);
writeOutput("duplicateStruct: #arrayContains(data.test, elementToFind)#<br />");

// test duplicate object
elementToFind = duplicate(data.object);
writeOutput("duplicateObject: #arrayContains(data.test, elementToFind)#<br />");


// test different struct with same value
elementToFind = {one="tahi"};
writeOutput("equivalentStruct: #arrayContains(data.test, elementToFind)#<br />");

// test different struct with same value, but different case
elementToFind = {one="TAHI"};
writeOutput("equivalentStructDifferentCase: #arrayContains(data.test, elementToFind)#<br />");

// test different struct with same key, different value
elementToFind = {one="ichi"};
writeOutput("differentStructSameKeyDifferentValue: #arrayContains(data.test, elementToFind)#<br />");

// test different struct with different key and value
elementToFind = {seven="ono"};
writeOutput("completelyDifferentStruct: #arrayContains(data.test, elementToFind)#<br />");

// test equivalent array with same values, but different case
elementToFind = ["RUA", "TORU"];
writeOutput("equivalentArrayDifferentCase: #arrayContains(data.test, elementToFind)#<br />");

// test different array
elementToFind = ["whitu", "waru"];
writeOutput("completelyDifferentArray: #arrayContains(data.test, elementToFind)#<br />");

// different object, same value
elementToFind = new Number("rima");
writeOutput("equivalentObject: #arrayContains(data.test, elementToFind)#<br />");

// different object, same value, different case
elementToFind = new Number("RIMA");
writeOutput("equivalentObjectDifferentCase: #arrayContains(data.test, elementToFind)#<br />");

// different object, different value
elementToFind = new Number("iwa");
writeOutput("completelyDifferentObject: #arrayContains(data.test, elementToFind)#<br />");


// Number.cfc
component {

    public Number function init(number){
        variables.number = arguments.number;
        this.number      = variables.number;
        
        return this;
    }
    
}

The difference for each function's sample code that I've run to generate the results below is that - obviously - they call the relevant function being tested in place of arrayContains(), and for the arrayFindAll() & arrayFindAllNoCase() test I do an arrayLen() stubs three function call.

The results for each are as follows (I've omitted the dump for all bar the first example):


arrayContains()



Baseline data - array
1
Baseline data - struct
ONEtahi
2
Baseline data - array
1rua
2toru
3wha
4
Baseline data - component shared.blog.arrays.Number
NUMBERrima
METHODS


simple: YES
simpleDifferentCase: NO
struct: YES
array: YES
object: YES
assignment: YES
shallow: YES
duplicateStruct: YES
duplicateObject: YES
equivalentStruct: YES
equivalentStructDifferentCase: YES
differentStructSameKeyDifferentValue: NO
completelyDifferentStruct: NO
equivalentArrayDifferentCase: NO
completelyDifferentArray: NO
equivalentObject: YES
equivalentObjectDifferentCase: NO
completelyDifferentObject: NO


arrayFind()

simple: 3
simpleDifferentCase: 0
struct: 1
array: 2
object: 4
assignment: 2
shallow: 1
duplicateStruct: 1
duplicateObject: 4
equivalentStruct: 1
equivalentStructDifferentCase: 1
differentStructSameKeyDifferentValue: 0
completelyDifferentStruct: 0
equivalentArrayDifferentCase: 0
completelyDifferentArray: 0
equivalentObject: 4
equivalentObjectDifferentCase: 0
completelyDifferentObject: 0


arrayFindNoCase()

simple: 3
simpleDifferentCase: 3
struct: 1
array: 2
object: 4
assignment: 2
shallow: 1
duplicateStruct: 1
duplicateObject: 4
equivalentStruct: 1
equivalentStructDifferentCase: 1
differentStructSameKeyDifferentValue: 0
completelyDifferentStruct: 0
equivalentArrayDifferentCase: 0
completelyDifferentArray: 0
equivalentObject: 4
equivalentObjectDifferentCase: 0
completelyDifferentObject: 0


arrayFindAll()

simple: 1
simpleDifferentCase: 0
struct: 1
array: 1
object: 1
assignment: 1
shallow: 1
duplicateStruct: 1
duplicateObject: 1
equivalentStruct: 1
equivalentStructDifferentCase: 1
differentStructSameKeyDifferentValue: 0
completelyDifferentStruct: 0
equivalentArrayDifferentCase: 0
completelyDifferentArray: 0
equivalentObject: 1
equivalentObjectDifferentCase: 0
completelyDifferentObject: 0


arrayFindAllNoCase()

simple: 1
simpleDifferentCase: 1
struct: 1
array: 1
object: 1
assignment: 1
shallow: 1
duplicateStruct: 1
duplicateObject: 1
equivalentStruct: 1
equivalentStructDifferentCase: 1
differentStructSameKeyDifferentValue: 0
completelyDifferentStruct: 0
equivalentArrayDifferentCase: 0
completelyDifferentArray: 0
equivalentObject: 1
equivalentObjectDifferentCase: 0
completelyDifferentObject: 0

I've highlighted where the bugs are, and will discuss these in a bit.

I actually think the results here (bugs aside) are encouraging. The fact that every single one of them has pretty obvious bugs is not encouraging of the Adobe QA process though. Still: looking at the glass-half-full: I was half expecting the complex-object look-ups to only work if looking-up the exact same object reference. But they all work OK with different objects (and I mean arrays, structs and objects when I say that) with the same values, which was pleasingly surprising. Especially with actual CFC-instance objects, it didn't just do a type-check, it considers the object's state too (I didn't test this as thoroughly as I could have, actually, but I decided this was already a lot of code).

Another thing I didn't check is whether they look in array dimensions after the first. I just assumed they didn't, based on the values they returned: there's no way with a boolean, integer or an array to usefully identify matches across other dimensions (sounds like something from a '50s science fiction movie).

Bugs

  • In arrayContains(), a false positive is returned on a struct with different-cased subkeys. arrayContains() is supposed to be case-sensitive. Raised as 3316760.
  • arrayFind() has the same problem (3316763).
  • arrayFindNoCase() does not work for arrays or objects which have case-differences, returning "false negatives" in these cases (3316776).
  • arrayFindAll() and arrayFindAllNoCase(), similarly has the same problems as arrayFind() and arrayFindNoCase() (3316788 and 3316784 repestively).
  • and if we have arrayFind() / arrayFindNoCase() and arrayFindAll() / arrayFindAllNoCase()... where's arrayContainsNoCase()? Far be it for me to advocate yet another function that does pretty-much what other functions already cover, but if this job had been done thoroughly, then we ought to have an arrayContainsNoCase(). I hasten to add that if Adobe decides they want to be creating yet another array-look-up function, then my suggested arraySearch() would be what to aim for over arrayFindNoCase(). I'm not raising a bug for this in case Adobe actually create the a arrayContainsNoCase() function.
  • Another thing mentioned in the Railo bug tracker: the fact there's both "contains" type functions and "find" type functions is analogous to the similarly named list functions (listFind(), listContains(). This being the case, arrayContains() should really find a partial match within string values, not exact / complete matches. I think Adobe messed up there.
[UPDATE]

Results from Railo:
  • none of the functions work properly on Railo, so I could not run the tests. Railo does not support anything other than a string for the second argument. I have taken this up with the Railo bods on their forum, and raised a bug for it.
Results from OpenBD:
  • arrayContains(): fails the equivalentObject test (bug 487 raised).
  • arrayFind(): also fails the equivalentObject test.
  • arrayFindNoCase(): fails: equivalentStructDifferentCase, equivalentArrayDifferentCase, equivalentObject, equivalentObjectDifferentCase.
  • arrayFindAll() and arrayFindAllNoCase() are not supported.

arrayDelete()

arrayDelete() works kind of the same way as arrayContains(), except it deletes the matched element from the array. As with arrayContains() it mostly works, but is buggy (see below):

struct function getTestData(){
    var data = {};
    data.struct    = {one="tahi"};
    data.array    = ["rua", "toru"];
    data.simple    = "wha";
    data.object    = new Number("rima");  // see Number.cfc below
    
    data.test = [
        data.struct,
        data.array,
        data.simple,
        data.object
    ];
    return data;
}

string function ok(array){
    return arrayLen(array) == 3 ? "DELETED" : "NOT DELETED";
}


// test just a simple value
data = getTestData();
writeDump(var=data, label="Baseline data for all tests");

arrayDelete(data.test, "wha");
writeOutput("Delete simple value: #ok(data.test)#<br />");


// simple value but different case
data = getTestData();
arrayDelete(data.test, "WHA");
writeOutput("Delete simple value, different case: #ok(data.test)#<br />");

// one each of an array, struct, object, using the actual value from the array itself
data = getTestData();
arrayDelete(data.test, data.test[1]);
writeOutput("Delete struct: #ok(data.test)#<br />");

data = getTestData();
arrayDelete(data.test, data.test[2]);
writeOutput("Delete array: #ok(data.test)#<br />");

data = getTestData();
arrayDelete(data.test, data.test[4]);
writeOutput("Delete object: #ok(data.test)#<br />");


// test copy by assignment
data = getTestData();
elementToFind = data.array;
arrayDelete(data.test, elementToFind);
writeOutput("Delete assigned array: #ok(data.test)#<br />");


// test shallow copy
data = getTestData();
elementToFind = structCopy(data.struct);
arrayDelete(data.test, elementToFind);
writeOutput("Delete shallow-copied struct: #ok(data.test)#<br />");


// test duplicate atruct
data = getTestData();
elementToFind = duplicate(data.struct);
arrayDelete(data.test, elementToFind);
writeOutput("Delete duplicated struct: #ok(data.test)#<br />");


// test duplicate object
data = getTestData();
elementToFind = duplicate(data.object);
arrayDelete(data.test, elementToFind);
writeOutput("Delete duplicated object: #ok(data.test)#<br />");



// test different struct with same value
data = getTestData();
elementToFind = {one="tahi"};
arrayDelete(data.test, elementToFind);
writeOutput("Delete equivalent but different struct: #ok(data.test)#<br />");


// test different struct with same value, but different case
data = getTestData();
elementToFind = {one="TAHI"};
arrayDelete(data.test, elementToFind);
writeOutput("Delete equivalent struct with different-cased key: #ok(data.test)#<br />");


// test different struct with same key, different value
data = getTestData();
elementToFind = {one="ichi"};
arrayDelete(data.test, elementToFind);
writeOutput("Delete different struct with different key value: #ok(data.test)#<br />");


// test different struct with different key and value
data = getTestData();
elementToFind = {seven="ono"};
arrayDelete(data.test, elementToFind);
writeOutput("Delete different struct: #ok(data.test)#<br />");


// test equivalent array with same values, but different case
data = getTestData();
elementToFind = ["RUA", "TORU"];
arrayDelete(data.test, elementToFind);
writeOutput("Delete equivalent array with different case: #ok(data.test)#<br />");



// test different array
data = getTestData();
elementToFind = ["whitu", "waru"];
arrayDelete(data.test, elementToFind);
writeOutput("Delete different array: #ok(data.test)#<br />");


// different object, same value
data = getTestData();
elementToFind = new Number("rima");
arrayDelete(data.test, elementToFind);
writeOutput("Delete different object with same value: #ok(data.test)#<br />");


// different object, same value, different case
data = getTestData();
elementToFind = new Number("RIMA");
arrayDelete(data.test, elementToFind);
writeOutput("Delete different object with same value, different case: #ok(data.test)#<br />");


// different object, different value
data = getTestData();
elementToFind = new Number("iwa");
arrayDelete(data.test, elementToFind);
writeOutput("Delete completely different object: #ok(data.test)#<br />");



Baseline data for all tests - struct
ARRAY
Baseline data for all tests - array
1rua
2toru
OBJECT
Baseline data for all tests - component shared.blog.arrays.Number
NUMBERrima
METHODS
SIMPLEwha
STRUCT
Baseline data for all tests - struct
ONEtahi
TEST
Baseline data for all tests - array
1
Baseline data for all tests - struct
ONEtahi
2
Baseline data for all tests - array
1rua
2toru
3wha
4[see cfc1 for Number details]


Delete simple value: DELETED
Delete simple value, different case: NOT DELETED
Delete struct: DELETED
Delete array: DELETED
Delete object: DELETED
Delete assigned array: DELETED
Delete shallow-copied struct: DELETED
Delete duplicated struct: DELETED
Delete duplicated object: DELETED
Delete equivalent but different struct: DELETED
Delete equivalent struct with different-cased key: DELETED
Delete different struct with different key value: NOT DELETED
Delete different struct: NOT DELETED
Delete equivalent array with different case: NOT DELETED
Delete different array: NOT DELETED
Delete different object with same value: DELETED
Delete different object with same value, different case: NOT DELETED
Delete completely different object: NOT DELETED

This function needs to make its mind up if it's case-sensitive, or not case-sensitive. It needs to be one or the other. So I'm not sure which of the orange bit or the green bit is evidence of a bug, but one of them is (raised as bug 3316798). The docs don't say whether it's supposed to be case-sensitive or not. They should.

One thing to know here is that arrayDelete() only deletes one match, and there's no way to make it do otherwise. If one wants to get rid of all matches for a given object, then one needs to loop over the array. Here's an example:

a = ["same","same","not same","same","same"];
writeDump(var=a, label="Initial state");
arrayDelete(a, "same");
writeDump(var=a, label="After call to arrayDelete()");

while (arrayContains(a, "same")){
    arrayDelete(a, "same");
}
writeDump(var=a, label="After looped call to arrayDelete()");

This yields:

Initial state - array
1same
2same
3not same
4same
5same
After call to arrayDelete() - array
1same
2not same
3same
4same
After looped call to arrayDelete() - array
1not same

I think it'd be handy if arrayDelete() had a "scope" (or something similar) argument that'd get rid of all objects that match. That said: I don't see it as a particularly useful function at all anyhow, so I'm not that fussed: I don't recall a time that I wanted to remove something from an array based on its value. No doubt other people have had to do this, so fair enough.

I ran some tests on Railo, and found it has a bug in that it cannot cope with complex objects in the array. Sample code:

test = [
    {one="tahi"},
    ["rua", "toru"],
    "wha"
];

try {
    arrayDelete(test, "WHA");
    writeDump(test);
} catch (any e){
    writeOutput("#e.message# #e.detail#<br />");
}

test = [
    "tahi",
    {two="rua"},
    ["toru", "wha"]
];

try {
    arrayDelete(test, "tahi");
    writeDump(test);
} catch (any e){
    writeOutput("#e.message# #e.detail#<br />");
}

I've raised this as RAILO-2046, and cross referenced it on their forums.

OpenBD does not have an arrayDelete() function.

arrayEach()

arrayEach() loops over an array, passing each element to a callback function which can then do [something] to the element. The docs identify this as a "closure function", but as I'll demonstrate it's just a function that takes a callback. The callback doesn't specifically need to be a closure; any old (user-defined) function would do. The docs say the function needs to be inline: it doesn't need to be (as demonstrated further down).

Here's an example of specifying the callback inline:

a = ["Tahi","Rua","Toru","Wha","Rima","Ono","Whitu","Waru","Iwa","Tekau"];

arrayEach(
    a,
    function(element){
        param name="i" default=1;
        writeOutput("#i++# #uCase(element)#<br />");
    }
);

This outputs:
1 TAHI
2 RUA
3 TORU
4 WHA
5 RIMA
6 ONO
7 WHITU
8 WARU
9 IWA
10 TEKAU

This example is very contrived, but you can see the power that these iterator/callback functions have. The callback can do anything you need it to, to each element of the array.

There's one thing to observe here: only the element gets passed to the callback. If I want to know which index each element is at, I need to infer that myself (ie: with the i variable that I maintain "by hand") . arrayEach() would be a lot more fully-realised if its callback was also passed the index. I think that a very high percentage of the time, one would want to know which element one is looking at, as well as the element value. It's a bit "jerry-built" to have to roll one's own.

Oh, another quick observation. Note that i is not a function-local variable, it needs to be created in the context of the calling code so that its value persists across iterations. Another reason that it would be better if the index was just passed into the callback. I'll file an E/R for this, and cross-reference it back here (331680).

Here's a demonstration of using a plain-old-function rather than a closure as the callback. As well as it not needing to be inline, as per the docs:

a = ["Tahi","Rua","Toru","Wha","Rima","Ono","Whitu","Waru","Iwa","Tekau"];

void function eachHandler(element){    // not a closure
    param name="i" default=1;
    writeOutput("#i++# #uCase(element)#<br />");
}

arrayEach(a, eachHandler); // and not inline

(The output for this is identical to the previous example).

To be honest, coding the callback inline always looks a bit slapdash to me, and I much prefer the functions to be implemented in on place, and then called in another place. Doing it inline to me seems to be like the difference between CF5 having UDFs and CFMX6+ having CFCs.

One red flag that waved briefly before my eyes was whether arrayEach() would cope with sparse arrays. I'm pleased to say it does!

Here's an example of this in action:

a = [];
a[1] = "Tahi";
// no second element
a[3] = "Toru";
// no fourth element
a[5] = "Rima";
// no sixth element
a[7] = "Whitu";
// no eighth element
a[9] = "Iwa";
// no tenth element
a[11] = "tekau ma tahi";
arrayDeleteAt(a, 11);    // leaves the last element, 10, empty

arrayEach(
    a,
    function(element){
        if (isDefined("element")){
            writeOutput(uCase(element));
        }else{
            writeOutput("Not defined");
        }
        writeOutput("<br />");
    }
);

Output:

TAHI
Not defined
TORU
Not defined
RIMA
Not defined
WHITU
Not defined
IWA
Not defined

So that's good.

Railo works exactly the same way as CF does here. OpenBD does not have this function.


arrayFilter()

This function iterates over an array and passes each element to the callback like arrayEach() does, except the callback returns true or false depending on whether to keep or discard the element, and after iterating over all the elements either keeping or discarding them, a new array is returned with the results of the filter process.

Here are examples of using this function using both an inline callback and "here's one I made earlier":

arrays.all = ["Tahi","Rua","Toru","Wha","Rima","Ono","Whitu","Waru","Iwa","Tekau"];

arrays.odds = arrayFilter(
    arrays.all,
    function(element){
        param name="i" default=1;
        return i++ MOD 2;
    }
);
writeDump(arrays);

arrays.all = ["Tahi","Rua","Toru","Wha","Rima","Ono","Whitu","Waru","Iwa","Tekau"];

boolean function oddsOnly(element){
    param name="i" default=1;
    return i++ MOD 2;
}


arrays.odds = arrayFilter(
    arrays.all,
    oddsOnly
);
writeDump(arrays);

The output for both is as follows:


struct
ALL
array
1Tahi
2Rua
3Toru
4Wha
5Rima
6Ono
7Whitu
8Waru
9Iwa
10Tekau
ODDS
array
1Tahi
2Toru
3Rima
4Whitu
5Iwa

Not that the docs for this are buggy as they incorrectly list arrayElement as an argument of arrayFilter(), but it's actually an argument of the callback. Also Adobe didn't bother putting a code example on this page, which ain't great. I've annotated the page with both these points, plus added a code sample.

Railo works the same as CF does for arrayFilter(). Again, OpenBD does not have this function.

arraySlice()

arraySlice() allows one to extract a section of an array, and return it. For example:

original = ["Tahi","Rua","Toru","Wha","Rima","Ono","Whitu","Waru","Iwa","Tekau"];
writeDump(var=original, label="Original data");

new = arraySlice(original, 2, 3);
writeDump(var=new, label="Rua-Wha");

new = arraySlice(original, 4);
writeDump(var=new, label="Wha-Tekau");

new = arraySlice(original, -5, 3); 
writeDump(var=new, label="Ono-Waru");

Output:

Original data - array
1Tahi
2Rua
3Toru
4Wha
5Rima
6Ono
7Whitu
8Waru
9Iwa
10Tekau
Rua-Wha - array
1Rua
2Toru
3Wha
Wha-Tekau - array
1Wha
2Rima
3Ono
4Whitu
5Waru
6Iwa
7Tekau
Ono-Waru - array
1Ono
2Whitu
3Waru


The slightly unexpected thing (compared to all other array functions in CF) is that it also supports a negative offset, meaning one can slice the array from the end of it, rather that start at the beginning. It's a bit PHP-ish to support this on one array function, but not the other ones that could legitimately do so, eg: arrayDeleteAt(), arrayInsertAt(), etc. Even just array notation could work like this, where a[-1] is the last element in the array, a[-2] is the last but one, etc. Adobe shouldn't roll this sort of thing out to just one function in a library because that's the one it occurred to them to do so.

Railo has some anomalous behaviour here. For the third option, it simply returns the 5th-7th elements, ie: it treats "-5" as "5". It should either support negative indexes in the same way CF does, or it should reject "-5" as a valid option. I'm not sure what their intention is here, so have asked on their forums. Once I know what their expections are, I'll update this note.

OpenBD doesn't support negative indexes, but it behaves correctly here in that it outright rejects them, rather than just using their absolute value.

Again, I was wondering if arraySlice() would cope with sparse arrays, and it does:

original = [];
original[1] = "Tahi";
original[3] = "Toru";
original[5] = "Rima";
original[7] = "Whitu";
original[9] = "Iwa";
original[11] = "tekau ma tahi";
arrayDeleteAt(original, 11);    // leaves the last element, 10, empty

writeDump(var=original, label="Original data");

new = arraySlice(original, 2, 3);
writeDump(var=new, label="Rua-Wha");

new = arraySlice(original, 4);
writeDump(var=new, label="Wha-Tekau");

new = arraySlice(original, -5, 3); 
writeDump(var=new, label="Ono-Waru");

Output:

Original data - array
1Tahi
2[undefined array element] Element 2 is undefined in a Java object of type class coldfusion.runtime.Array.
3Toru
4[undefined array element] Element 4 is undefined in a Java object of type class coldfusion.runtime.Array.
5Rima
6[undefined array element] Element 6 is undefined in a Java object of type class coldfusion.runtime.Array.
7Whitu
8[undefined array element] Element 8 is undefined in a Java object of type class coldfusion.runtime.Array.
9Iwa
10[undefined array element] Element 10 is undefined in a Java object of type class coldfusion.runtime.Array.
Rua-Wha - array
1[undefined array element] Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
2Toru
3[undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array.
Wha-Tekau - array
1[undefined array element] Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
2Rima
3[undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array.
4Whitu
5[undefined array element] Element 5 is undefined in a Java object of type class coldfusion.runtime.Array.
6Iwa
7[undefined array element] Element 7 is undefined in a Java object of type class coldfusion.runtime.Array.
Ono-Waru - array
1[undefined array element] Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
2Whitu
3[undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array.

OpenBD is buggy with this. It's output is:

Original data - array
1Tahi
2[undefined array element]
3Toru
4[undefined array element]
5Rima
6[undefined array element]
7Whitu
8[undefined array element]
9Iwa
10[undefined array element]
Rua-Wha - array
1Toru
2[undefined array element]
3Rima
Wha-Tekau - array
1Rima
2[undefined array element]
3Whitu
4[undefined array element]
5Iwa
6[undefined array element]


(I've removed the last example as we already know it won't run on OpenBD. Note that OpenBD ignores empty elements when looking up the starting point to slice at. This is not right. I've brought this to their attention.

Conclusion

OK, so I think - in this and the previous article - I've actually covered all the array functions now. Please do let me know if I've forgotten any, and I'll get it sorted. What I haven't done yet is to have a look at all the other functionality CFML offers for working with arrays: other data-types have functions one can apply to them to return an array based on elements of that data (eg: structSort()), as well as general usage like looping over arrays, serializing them, etc. That'll all be rolled into one last article. Then I can move on. And so can you.

Righto.

--
Adam

PS: I haven't yet run any of those code on Railo or OpenBD to see whether there's any note-worthy idiosyncrasies, bugs or omissions. I will get onto that, and update this article accordingly (or if there's enough to discuss: another article).