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 |
| ||||
| 2 |
| ||||
| 3 |
| ||||
| 4 |
| ||||
| 5 |
| ||||
Railo does this a bit better, by creating a new struct for each element (I imagine by duplicating the input value):
| Array | ||||||||
| 1 |
| |||||||
| 2 |
| |||||||
| 3 |
| |||||||
| 4 |
| |||||||
| 5 |
| |||||||
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 | |
|---|---|
| 1 | FA2D3162-AECF-4E3B-3F01F31D1129D67D |
| 2 | FA2D3162-AECF-4E3B-3F01F31D1129D67D |
| 3 | FA2D3162-AECF-4E3B-3F01F31D1129D67D |
| 4 | FA2D3162-AECF-4E3B-3F01F31D1129D67D |
| 5 | FA2D3162-AECF-4E3B-3F01F31D1129D67D |
| array | |
|---|---|
| 1 | FA2D3CE6-0DE8-A6C4-852ABAA06B006B63 |
| 2 | FA2D3CEF-EE50-E2CB-FD9C4579784BA59E |
| 3 | FA2D3CF7-E1CE-C4D2-10FE504DBE2A1256 |
| 4 | FA2D3CFE-BFFD-FCCC-DB947FC2B556C379 |
| 5 | FA2D3D06-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 | |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| array | |
|---|---|
| 1 | tahi |
| 2 | rua |
| 3 | toru |
| 4 | wha |
| 5 | rima |
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