Friday, 11 April 2014

Railo 4.2.0.007 is out: more new iteration methods

G'day:
Micha posted on the Railo Google Group the other day "(Last) Railo beta release (4.2.0.007)". You can get this via the in-admin updater if you're set to use the "Development releases (Bleeding Edge)" update channel.

Staying true to form, the upgrade process is seamless, and it also offers a bunch of excellent new stuff, as well as bug fixes. This is definitely the way CFML should be heading... not waiting around two years between features, as we have been with ColdFusion.

There's a swag of new stuff, but I'm just gonna focus on the added iteration methods: for queries and lists.

ColdFusion has had some list iteration functions since ColdFusion 10, and adds more in ColdFusion 11. I discuss them in "ColdFusion 11: .map() and .reduce()". Railo hadn't implemented them, I think because they - somewhat dogmatically - don't consider lists to be a "type", therefore only consider string functions to be appropriate for strings. This is slightly specious IMO because Railo does have all the other list functions, after all. Anyway, they've added them now.

Here's example usage of the list-iteration member functions: .each(), .filter(), .every(), .some(), .map(), .reduce(). They have not implemented a list-sorting iteration function, for some reason. This code is also on GitHub: list.cfm.

rainbow = "whero,karaka,kowhai,kakariki,kikorangi,tawatawa,mawhero"

"first,second".each(function(){
    dump(arguments)
})

rainbow.each(function(element,index,list){
    echo("#index#/#listLen(list)#: #element#<br>")
})

Output:
Scope Arguments
11
stringfirst
22
number1
33
stringfirst,second
Scope Arguments
11
stringsecond
22
number2
33
stringfirst,second

1/7: whero
2/7: karaka
3/7: kowhai
4/7: kakariki
5/7: kikorangi
6/7: tawatawa
7/7: mawhero


There's a few things to note here:

  • Note how one can call a method directly on a string literal here? Cool.
  • Micha said he'd not implemented the list iteration functions as methods, but it seems he has.
  • And all the list iteration callback functions receive three arguments: the current element value, the index of the value in the list, and the list itself. There's a slight bug here in that they really need to pass the list delimiter into the callback too: all list functions need to know the list delimiter; otherwise one can't really do anything with that list argument.

result = rainbow.filter(function(element,index){
    return !element.startsWith("k")
})
echo("The colours not starting with k are: #result#<br>")

Output:
The colours not starting with k are: whero,tawatawa,mawhero

filter() is fairly straight forward, and works exactly how one would expect.

/*
result = rainbow.sort(function(e1,e2){
    return sgn(e1.len() - e2.len())
})
echo("sorted colours: #result#<br>")
*/

.sort() (nor listSort()) have been implemented to have a callback-centric flavour. This is a strange omission. ColdFusion has implemented one either. Needless to say if there's a case of any iteration functions, there's a case for all of them.

longest = 0
result = rainbow.every(function(element,index){
    echo("Checking: #element#<br>")
    var length = element.len()
    var longer    = length >= longest
    longest = max(longest,length)
    return longer
})

echo("Every colour is at least as long as the previous: #result#<br>")

Output:
Checking: whero
Checking: karaka
Checking: kowhai
Checking: kakariki
Checking: kikorangi
Checking: tawatawa
Every colour is at least as long as the previous: false


Again, this is just demonstrating that the function works as per all the other every() implementations. I've not much to add.

result = rainbow.some(function(element,index){
    echo("Checking: #element#; ")
    var vowels = element.reReplaceNoCase("[^aeiou]", "", "all")
    var vowelsLen = vowels.len()
    echo("#vowels#: #vowelsLen#; #vowelsLen >= 4#<br>")
    return vowelsLen >= 4
})

echo("Some maori colours have at least four vowels in them: #result#<br>")

Output:
Checking: whero; eo: 2; false
Checking: karaka; aaa: 3; false
Checking: kowhai; oai: 3; false
Some maori colours have at least four vowels in them: true


I'm outputing some telemetry here, because I was verifying what seems to be a bug. As far as I can determine, I should also be seeing this:

kakariki; aaii: 4; true

But it's never showing up. So that looks like a bug to me?


newRainbow = listChangeDelims(rainbow, "|")
backwards = newRainbow.map(function(element,index){
    return element.reverse()
}, "|")
dump(backwards)

Output:

stringorehw|akarak|iahwok|ikirakak|ignarokik|awatawat|orehwam

Here I'm just making sure that these functions can also take a delimiter. Even if it's not then passed through to the callback.


"first,second".reduce(function(){
    dump(arguments)
    return ""
},"")

tally = rainbow.reduce(function(prev,current){
    return prev + current.len()
}, 0)
dump(tally)

Output:

Scope Arguments
11
string
22
stringfirst
33
number1
44
stringfirst,second
Scope Arguments
11
string
22
stringsecond
33
number2
44
stringfirst,second
number49

Just a reminder: the reduce() function also takes an optional starting value, and the callback doesn't receive the "usual suspects", it receives the starting value (or the previous iteration's result), the current element, its index, and the whole list.

Here my example just sums up the length of all the elements.

That's all cool. I was gonna go through the query iteration functions too, but I'm out of lunch time, so need to press "send". I'll raise some bugs and cross-ref them back here once I get a moment.

Bugs:

  • the callbacks need to receive the list delimiter: RAILO-3030;
  • listSome() premature exit: RAILO-3029;
  • I'd already raise the fact listSort() is missing: RAILO-2934.
--
Adam