Sunday 13 October 2013

CFML: arraySetEach()

G'day:
I had a conversation on IRC the other day with Jason Dean about a shortcoming of arraySet().


The issue is that array set works like this:

arraySet(array, from, to, value)

So like this:

sameStructs = [];
arraySet(sameStructs, 1, 5, {});
sameStructs[3].foo = "bar";
writeDump(sameStructs);

Unfortunately - as you might guess from the variable name - ColdFusion only evaluates the value once - meaning each array element has the same structure. So this code outputs:

array
1
struct
FOObar
2
struct
FOObar
3
struct
FOObar
4
struct
FOObar
5
struct
FOObar

Railo does this a bit better, by creating a new struct for each element (I imagine by duplicating the input value):

Array
1
Struct
2
Struct
3
Struct
foo
stringbar
4
Struct
5
Struct

So if you were on Railo, the rest of this article is perhaps a waste of time. I'm working around a ColdFusion CFML shortfall here.

What ColdFusion needs is an arraySetEach() function, which is a hybrid of arraySet(), and arrayEach(). So that each element being set take the return value of a callback function. Here's one I'm gonna submit to CFLib (and being the moderator, I suspect it'll get approved, too ;-) :

array function arraySetEach(required array array, required numeric from, required numeric to, required function callback){
    arrayset(array, from, to, "");
    for (var i=from; i <= to; i++){
        array[i] = callback(index=i, argumentCollection=arguments);
    }
    return array;
}

Easy. Instead of giving the function just a value to populate each array element with, we pass it a function, and each array element gets the return value from the function. Here's a basic example using each of arraySet() and arraySetEach():

uuids = [];
arraySet(uuids, 1, 5, createUuid());
writeDump(uuids);

uuids = arraySetEach([], 1, 5, function(){
    return createUuid();
});
writeDump(uuids);

array
1FA2D3162-AECF-4E3B-3F01F31D1129D67D
2FA2D3162-AECF-4E3B-3F01F31D1129D67D
3FA2D3162-AECF-4E3B-3F01F31D1129D67D
4FA2D3162-AECF-4E3B-3F01F31D1129D67D
5FA2D3162-AECF-4E3B-3F01F31D1129D67D


array
1FA2D3CE6-0DE8-A6C4-852ABAA06B006B63
2FA2D3CEF-EE50-E2CB-FD9C4579784BA59E
3FA2D3CF7-E1CE-C4D2-10FE504DBE2A1256
4FA2D3CFE-BFFD-FCCC-DB947FC2B556C379
5FA2D3D06-E1BB-EE43-FF6D0501E8831384

To belabour the point: arraySet() calls createUuid() once, and uses that value for each element. Whereas arraySetEach() calls createUuid() for each element.

This code is, btw, a good example of where having CFML inbuilt functions being first-class functions would be useful. Ideally I would not need to wrap createUuid() in a bespoke function to use it as a callback:

function(){
    return createUuid();
}

I should just be able to do this:

uuids = arraySetEach([], 1, 5, createUuid);

Where createUuid is itself the callback. Adobe is apparently adding this in ColdFusion 11.

Here are some more proofs of concept:

sequence = arraySetEach([], 1, 5, function(){
    return index;
});
writeDump(sequence);

fromList = arraySetEach([], 1, 5, function(){
    return listGetAt("tahi,rua,toru,wha,rima", index);
});
writeDump(fromList);

array
11
22
33
44
55

array
1tahi
2rua
3toru
4wha
5rima

So this demonstrates, I s'pose, how this approach would benefit Railo CFML too: instead of just providing a dumb value to set in the array, one can have any functionality one wants.

Anyway, I'm at the bar @ CFCamp, and people want to talk to me so at this fairly disconnected juncture, I'm gonna "press send". I'll put a draft of that function up on CFLib, and link back.

I've raised a ticket to get this added to CFML: 4022432.

--
Adam