Yeah, this is an odd thing to write about, and there's no trick to it: I really do mean this sort of thing:
for i=1 to 10 step 2
I got to thinking about this because of a comment from Chris Dawes on my article Lucee 5 beta:
<cfloop>
tweak, in which he sung the praises of the CFML tag equivalent of that general construct:<cfloop index="i" from="1" to="10" step="2">
This is fine for a view, but I'd steer clear of it in my business logic. But what would I use instead? The obvious answer is this:
for (i=1; i <= 10; i+=2)
But these days I try to eschew generic looping like that, in favour of a more purposeful iteration exercise using iteration methods. If I was iterating a collection to remap data, I'd use
map()
; if I was doing so to translate the collection data to another data structure or object, I'd reduce()
; if I was getting rid of stuff I'd filter()
, and - if the language allowed it - if I was checking to see if one or any of the collection elements fulfilled some criteria, I'd use some()
or every()
. It's really seldom that one wants to loop "for the hell of it", if one thinks about it. It's an exercise in processing data or checking data.
Obviously in a view one is likely to be just outputting data from the data collection, so using a general loop is as good as anything. Also if one is skipping records... a stepped general purpose loop is perhaps better than using like an array-based loop and conditionally continuing the loop or some nonsense like that. All good.
But this got me thinking... let's say I was:
- writing business logic;
- wanting to step over array elements;
- still wanted to use array iteration methods.
This is more a mental exercise than a programming one, but it just happened to pique my interest.
CFML
In CFML this is dead easy. Here's an example:numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];
oddNumbers = numbers.filter(function(_,i){return i mod 2;});
writeDump(oddNumbers);
Here I'm using ColdFusion 12's CLI, and the results are as follows:
C:\src\cfml\languageComparison\steppedLoop>cf cfmlVersion.cfm
array
1) tahi
2) toru
3) rima
4) whitu
5) iwa
C:\src\cfml\languageComparison\steppedLoop>
array
1) tahi
2) toru
3) rima
4) whitu
5) iwa
C:\src\cfml\languageComparison\steppedLoop>
filter()
allows us to create an array of every second element simply by doing the usual mod operation. This gives us a new array.If we wanted to actually render the elements as a string, we'd just chain a
reduce()
call onto that:oddNumbersAsString = numbers.filter(function(_,i){return i mod 2;}).reduce(function(combined, oddNumber){
return "#combined##oddNumber##chr(10)#";
}, "");
CLI.writeLn(oddNumbersAsString);
C:\src\cfml\languageComparison\steppedLoop>cf cfmlVersion.cfm
tahi
toru
rima
whitu
iwa
C:\src\cfml\languageComparison\steppedLoop>
tahi
toru
rima
whitu
iwa
C:\src\cfml\languageComparison\steppedLoop>
Or, hey: not get so dogmatic about using collection iteration methods, and just leverage a list method:
oddNumbersAsString = numbers.filter(function(_,i){return i mod 2;}).toList(chr(10));
(yields the same output)
I turned my mind to other languages to compare.
JavaScript
Same as CFML really. Just some "spelling" differences:oddNumbersAsString = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
.filter(function(_,i){return (i+1) % 2;})
.reduce(function(combined, oddNumber){
return combined + oddNumber + String.fromCharCode(10);
}, "");
console.log(oddNumbersAsString);
Output:
C:\src\cfml\languageComparison\steppedLoop>node jsVersion.js
tahi
toru
rima
whitu
iwa
C:\src\cfml\languageComparison\steppedLoop>
tahi
toru
rima
whitu
iwa
C:\src\cfml\languageComparison\steppedLoop>
PHP
PHP is a bit disappointing here for a coupla reasons. Firstly PHP is still at its roots still procedural, so the code is intrinsically more clunky, plus it's collection iteration$numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];
$indexedNumbers = array_map(null, range(0, count($numbers)-1), $numbers);
$indexedOddNumbers = array_filter(
$indexedNumbers,
function($number){
return ($number[0]+1) % 2;
}
);
$reindexedIndexedOddNumbers = array_values($indexedOddNumbers);
$oddNumbers = array_map(
function($number){
return $number[1];
},
$reindexedIndexedOddNumbers
);
$oddNumbersAsString = array_reduce(
$oddNumbers,
function($combined, $oddNumber){
return $combined . $oddNumber . PHP_EOL;
},
""
);
echo $oddNumbersAsString;
The issues here are:
- we want to filter on the array index position, but PHP doesn't think to pass this to the
array_filter()
's callback, so I need to push an index into the data usingarray_map()
andrange()
. One good thing here is thatarray_map()
, if not given a callback then it just maps all the arrays it's passed together into sub arrays. Which suits my purposes here. - Then I can use
array_filter()
to do the actual filtering, butarray_filter()
is a bit sh!t for a second time because it leaves a sparse array after it filters. So after I filter I don't have a new array with elements 0-4, I have one with elements 0,2,4,6,8 (!!!!). How bloody stupid is that? I guess this is down to PHP's array implementation being quite rubbish and basically being "kinda an array, kinda a struct, kinda - as a result - neither". - I need to fix this by creating a new array with just the values.
- As I said PHP is procedural, so I can't chain stuff. I could nest it, but that makes for awful, inside-out code. So I use individual statements and intermediary variables.
- Having filtered I still need to get my data elements back to "normal" by getting rid of the index I embedded in them. Another call to
array_map()
to undo what I did before. - And finally I can reduce things back down to a string.
forEach()
loops. Old school, but they work.Clojure
I finally bit the bullet and decided to see how to do this exercise with Clojure too. I picked this cos Sean's piqued my interest in Clojure as he's always banging on about it, plus having looked at it it's completely alien to me, and it annoys me I basically can't read it. I've done(println "G'day World")
with Clojure in the past, but that's it. I took a deep breath, rolled up my sleeves, and got Google at the ready.After spending more time typing into Google and reading than I did typing in code, I came up with this:
(def numbers ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"])
(def indexed-numbers (map (fn [i,v] (vector i v)) (range) numbers))
(def odd-indexed-number (
filter (
fn [indexed-number] (= (rem (get indexed-number 0) 2) 0))
indexed-numbers
)
)
(def odd-numbers (
map (
fn [indexed-number] (get indexed-number 1))
odd-indexed-number
)
)
(println "via doseq")
(doseq [number odd-numbers] (println number))
(println "=======================")
(println "via reduce")
(def odd-numbers-as-string (
reduce (
fn [combined odd-number] (str combined odd-number "\n"))
""
odd-numbers
)
)
(print odd-numbers-as-string)
(println "=======================")
Firstly... this code is almost certainly complete shite as I don't know the first thing about Clojure. For pity's sake don't use it as an example of anything other than how crap I am at Clojure.
I think this is roughly analogous to the intent of the CFML code, although I've put some intermediary outputs in there whilst I was checking stuff, plus I output the odd numbers in a coupla different ways by way of experimentation. Clojure lists are ordered, but don't seem to have an index value per se, so I go through the same approach as I did in PHP (although for a more valid reason than with PHP, because PHP does have an index value; it's just not exposed when it ought to be): mapping an index value into the collection, which I can then filter on. I filter every second element out, and then map the elements of the result of that back to their original data structure (basically removing the index entry). Then I do two things: just iterate over that with
doseq
outputting each element; then going back to the "requirement" of reducing the list to a string, and outputting that.All in all I am quite pleased I managed to get my brain around that lot, and I don't find the code as scary as I expected. I can kinda read that code now. I can't read anyone else's Clojure code, but that code up there I can read.
And it all works:
C:\src\cfml\languageComparison\steppedLoop>lein repl
[bumpf removed]
user=> (load-file "clojure_version.clj")
via doseq
tahi
toru
rima
whitu
iwa
=======================
via reduce
tahi
toru
rima
whitu
iwa
=======================
nil
user=>
[bumpf removed]
user=> (load-file "clojure_version.clj")
via doseq
tahi
toru
rima
whitu
iwa
=======================
via reduce
tahi
toru
rima
whitu
iwa
=======================
nil
user=>
Java
Speaking of languages I'm crap at... I had read Java 8 added in some collection iteration stuff too, so I figured whilst I'm embarrassing myself with bad code, I'd work out how to do this in Java too. Blimey. What a bunch of horsing around that was. Here's the code I came up with...import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Consumer;
public class JavaVersion {
private static int i=0;
public static void main(String[] args){
Number[] numbersArray = getNumbersArray();
ArrayList<Number> numbersList = new ArrayList<Number>(Arrays.asList(numbersArray));
Stream<Number> numbersStream = numbersList.stream();
Function<Number,IndexedNumber> indexNumbers = getIndexMapHandler();
Stream<IndexedNumber> indexedNumbersStream = numbersStream.map(indexNumbers);
Predicate<IndexedNumber> filterOutEvens = getFilterHandler();
Stream<IndexedNumber> oddIndexedNumbersStream = indexedNumbersStream.filter(filterOutEvens);
Function<IndexedNumber,Number> deindexNumbers = getDeindexMapHandler();
Stream<Number> oddNumbersStream = oddIndexedNumbersStream.map(deindexNumbers);
Consumer<Number> renderOddNumbers = getNumberRenderer();
oddNumbersStream.forEach(renderOddNumbers);
}
private static Number[] getNumbersArray(){
Number[] numbersArray = {
new Number("one", "tahi"),
new Number("two", "rua"),
new Number("three", "toru"),
new Number("four", "wha"),
new Number("five", "rima"),
new Number("six", "ono"),
new Number("seven", "whitu"),
new Number("eight", "waru"),
new Number("nine", "iwa"),
new Number("ten", "tekau")
};
return numbersArray;
}
private static Predicate<IndexedNumber> getFilterHandler(){
Predicate<IndexedNumber> predicate = new Predicate<IndexedNumber>(){
@Override
public boolean test(IndexedNumber indexedNumber){
return indexedNumber.index % 2 != 0;
}
};
return predicate;
}
private static Function<Number,IndexedNumber> getIndexMapHandler(){
return new Function<Number,IndexedNumber>(){
@Override
public IndexedNumber apply(Number number){
return new IndexedNumber(++i, number);
}
};
}
private static Function<IndexedNumber,Number> getDeindexMapHandler(){
return new Function<IndexedNumber,Number>(){
@Override
public Number apply(IndexedNumber indexedNumber){
return indexedNumber.number;
}
};
}
private static Consumer<Number> getNumberRenderer(){
return new Consumer<Number>(){
@Override
public void accept(Number number){
System.out.println(number.en + ": " + number.mi);
}
};
}
}
class Number {
public String en;
public String mi;
public Number(String en, String mi){
this.en = en;
this.mi = mi;
}
}
class IndexedNumber {
public int index;
public Number number;
public IndexedNumber(int index, Number number){
this.index = index;
this.number = number;
}
}
I did a bloody cartwheel (no, not really) when I got that lot to compile and spit out something other than nonsense. What a frickin' drama to do something so bloody simple. No doubt I haven't taken the most direct route with this task, but the functional stuff - passing callbacks to facilitate mapping, filtering and reducing is just awful.
If you care to follow along, this performs the same logic as the PHP and Clojure versions: starting with an ordered list/array sort of thing, adding an index to it, filtering on that index, removing the index, reducing the result to a string. It blows my mind how I needed to use Functions to map, Predicates to filter and Consumers to reduce. And It took me ages to get my head around the... what is it... generics syntax with the
<stuff, and more stuff>
as it relates to those constructs.Horribly verbose stuff.
Update
I'm just updating this to include a coupla examples from other people who commented below. I've hoist them up here cos they're a handy comparison.Java redux: using lambdas
I have to admit I saw the lambda approach to this when I was first googling, but I wanted to work out how the hell the syntax for all the Function / Predicate / Consumer stuff worked, so did it that way. I ought to have also included the lambda approach which is much more terse. Joe Gooch has reminded me of this in his comment below. Now I'll not share Joe's example as it's not quite a one-to-one mapping of what I did in my Java example above, but I've done a second version of the code above not bothering with all the explicit Function / Predicate / Consumer objects, but just using inline lambdas. This version of main() just replaces the one in my earlier example:
public static void main(String[] args){
Number[] numbersArray = getNumbersArray();
new ArrayList<Number>(Arrays.asList(numbersArray))
.stream()
.map((number) -> new IndexedNumber(++i, number))
.filter((indexedNumber) -> indexedNumber.index % 2 != 0)
.map((indexedNumber) -> indexedNumber.number)
.forEach((number) -> System.out.println(number.en + ": " + number.mi));
}
Now that's more like it. The logic for the "callback" processing of the map / filter / map / forEach is very simple, so suits using an inline lambda expression. If they were more complicated than 1-2 statements, I'd still not take this approach.
But I will admit I kinda set Java up to fail in the example above.
Groovy
This one comes from Joe Gooch:numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];
numbers.findAll({ it -> numbers.indexOf(it)%2 != 1}).eachWithIndex({ it,idx -> println("${idx+1}: ${it}") })
And it outputs:
>groovy32 groovyVersionFromJoeGooch.groovy
1: tahi
2: toru
3: rima
4: whitu
5: iwa
>
1: tahi
2: toru
3: rima
4: whitu
5: iwa
>
Cheers fella. As I said in my response to you below, I really like Groovy and how one can achieve so much with so little code.
However on testing, I see that this only works on the basis of the value of the elements we step to being unique. For example check this out:
numbers = ["tahi", "tahi", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];
numbers.findAll({ it -> numbers.indexOf(it)%2 != 1}).eachWithIndex({ it,idx -> println("${idx+1}: ${it}") })
The output is:
1: tahi
2: tahi
3: toru
4: rima
5: whitu
6: iwa
2: tahi
3: toru
4: rima
5: whitu
6: iwa
One cannot rely on the elements values when one is actually interested in the index values. The relationship is - intrinsically - at best a coincidental one. I had the briefest of googles, but could not see a proper
filter()
method in Groovy, let alone one in which the callback receives the index value.
Joe's also sent me a Java variation, but I'm out of time this morning to look at it, so will need to check it out later.
Perl
Roger Tubby sent me a Perl example:my @numbers = qw/tahi rua toru wha rima ono whitu waru iwa tekau/;
my @oddNumbers = @numbers[map 2*$_, 0.. @numbers/2 ];
print join("\n", @oddNumbers );
And it does the business (I didn't recall installing Perl on this thing, but it's there, which is handy):
>perl perlVersionFromRogerTubby.pl
tahi
toru
rima
whitu
iwa
>
tahi
toru
rima
whitu
iwa
>
I don't know the first thing about Perl, so I'll just nod sagely and hastily move on before anyone asks me anything about it ;-)
Cheers dude.
Summary
Still... I realise I am not really engaging in a practical exercise here, and probably not taking the most expedient approach with either the Clojure or the Java solution. I did enjoy messing around with Clojure, and this is by far the most complex Java I have done (I'm not much further ahead with Java than "G'day world!" either, truth be told).Getting that Clojure code together probably took about three hours of reading and typing; the Java stuff took about the same. I reckon it was time well spent... I know a bit more about each now... and it was I think useful to compare all five languages.
I was gonna look at F# (a language I have never touched) and Groovy too... but the Java exercise drained my will to live, so I stopped.
On good thing now though... I feel I "get" Clojure enough to go back to my code quiz from over a year ago (Something for the weekend? A wee code quiz (in CFML, PHP, anything really...)) and look at the Clojure solutions, after having first implemented my own. So that's something. I do still need to work out how to write and run unit tests with it first though. TDD for everything, after all (except obviously I've just admitted I did not use TDD for this lot. Oops).
Now... someone please go rip my code to shreds. I'm off to get another Guinness.
Righto.
--
Adam