Pimp my Foreach Loop
Java’s foreach loop is useful but limited. Neither can we access the iterator of a running loop, nor can we write Ruby-style1 foreach loops, such as
1) In fact, this style of looping can be traced back to the ancient times of legendary Smalltalk.
names = words.select { |each| each.frist.upcase? }
bool = words.any? { |each| each.size > 7 }
length = words.inject(0) { |sum,each| sum + each.size }
We all know, this is because Java is – even though in its 6th release – still without the most basic structure of a programming language: Clo-clo-closures! (And rumours are, they wont fix that for the 7th release either.)
There are workarounds. Typically, anonymous inner-classes are (ab)used as closures. I dont like that. All the syntactic clutter and implementation overhead of using full-blown classes, only to inject some simple behavior into a loop? IMHO, not worth it.
Instead, here is a small DSL that pimps ye old Java foreach loop for you:
for (Select<String> each: select(words)) {
each.yield = each.value.charAt(0).isUppercase();
}
Collection<String> names = $result();
for (AnySatisfy<String> each: anySatisfy(words)) {
each.yield = each.value.length() > 7;
}
boolean bool = $result();
for (Inject<Integer,String> each: inject(0,words)) {
each.yield += each.value.length();
}
int length = $result();
How does it work?
Behind the scenes, Java’s foreach loop operates on an instance of Iterable. Thus we can hook a custom object into the loop and get called back upon starting and terminating the loop, as well as before and after each iteration.
In the first example, #select is a static method that wraps the given collection in a custom object. The custom object is of type Iterable<Select>, where Select has two fields. One field is used a input parameter, the other as output parameter. Before each iteration of the loop, value is populated with the current element. After each iteration, yield is polled for a boolean value. Inbetween, the loop is executed.
While running the loop, all elements for which the loop yields true are copied into a new collection. Upon terminating the loop, this collection is assigned to $result. To keep things thread-safe, the result is stored in a ThreadLocal variable .
The same technique is used in the two other examples. #anySatisfy checks if all iterations of the loop yield true. #inject combines all elements by injecting an accumulator value into each iteration of the loop.
The list of currently supported queries includes
AllSatisfyAnySatisfyCardinalityCollectCountCutPiecesDetectFoldGroupedByIndexOfInjectRejectSelect
If you need more, you can subclass For<Each,This<Each>> and implement your own query. As an example, I shall leave the implementation of Count here:
public class Count<Each> extends For<Each,Count<Each>> {
public Each value;
public boolean yield;
private int count;
protected void afterEach() {
if (yield) count++;
}
protected Object afterLoop() {
return count;
}
protected void beforeLoop() {
count = 0;
}
protected void beforeEach(Each element) {
value = element;
yield = false;
}
}
As usual, the complete sources are available on SCG subversion.
Have fun!
December 5th, 2008 at 00:09
One small correction: those Ruby-style foreach loops are in fact *Smalltalk*-style. This matters when you consider that Java came after Smalltalk and Ruby came after both.
December 5th, 2008 at 06:23
Hey Adrian, your hacks are really awesome.
But there’s one thing that I’ve been trying to accomplish and until now, no success.
I don’t even know if this is possible, but for what I’ve understood of how Roman Numbers were replaced, maybe you can figure out better (of course!) than me.
Java doesn’t allow us to use Enums as values for Object[] or String[] of Annotations. For example:
package com.some.auth.framwork; @interface AuthRoles { String[] values() default {}; } package com.mycompany.security; public enum MyRoles { ADMIN, USER; }and then, something like this
@AuthRoles(values={MyRoles.ADMIN, MyRoles.USER}) class MyPojo {}Do you think this is possible somehow?
Thanks! and good job!
December 5th, 2008 at 06:24
Oh, by the way… the reason I want to do this, is to have Enums instead of Strings for type-safety.
And of course, this doesn’t work:
@AuthRoles(values={MyRoles.ADMIN.name(), MyRoles.USER.name()}) class MyPojo {}:-)
December 5th, 2008 at 07:54
@Rudolf: Java obviously came after Ruby.
Peace
-stephan
December 5th, 2008 at 07:55
@Rudolf – you are so right! I’ve added a footnote promoting Smalltalk :)
@Bruno – I’ll take a look, if its part of the AST (which I assume) there should be a way to rewrite it. I guess you would like to replace the enums with their string value.
December 5th, 2008 at 08:00
Sorry for my last comment, both are from 1995, I thought Ruby was older (as a kind of “successor” to Perl, therefor the name).
Peace
-stephan
http://www.codemonkeyism.com
December 5th, 2008 at 09:48
FYI, “dont” isn’t a word — you might want to hire a proofreader.
I find it astonishing that anyone would consider closures to be “the most basic structure of a programming language” — they’re nice, sometimes, but they’re hardly basic. Don’t confuse your personal preferences with fundamentals, it only serves to harm your credibility with those who don’t (<– that’s a real English word) share your prejudices.
That being said, you have a slick idiom there. Emulating Smalltalk-style collection operations in Java with a minimum of syntax abuse is pretty cool.
Wishing for closures in Java, however, seems dangerous. They’d probably screw it up like they did generics. That would be worse than anonymous inner classes.
December 5th, 2008 at 22:58
SJS,
For your information, “FYI” is not a word - you might want to hire a proofreader.
Missing apostrophes are an abbreviation (as are three letter acronyms). In the same way, “don’t” is not a word either, but an abbreviation no doubt frowned upon by the slaves to language past who insisted instead on the correct usage of full “do not”.
December 5th, 2008 at 23:24
@SJS:
A missed apostrophe, in a non-native language, is hardly worthy of a snarky proofreading comment.
Are you truly *astonished* at the characterization? Did you gasp out loud? At *best* I was mildly intrigued at the characterization, although I do feel they’re at least *a* pretty basic technique.
I think, perhaps, you’re too comfortable with hyperbole and nit-picking.
December 5th, 2008 at 23:27
@Adrian: Way to emulate yield in Java. ;) It’s a little silly we need to mess with ThreadLocal for something that should be in the language but I guess that is the Java way of things.
@Bruno: Are you doing Wicket development? Looks like it. Indeed you can use Enum’s, but not in place of an Class. Imagine you could, what would the difference be between MyRoles.USER.name() and MyRoles.ADMIN.name()? None, both resolve to the same Enum class. Why don’t you just retrofit/overload it so that you can use Enum instead?
December 5th, 2008 at 23:53
Pretty nice. The trick with $result(); and TLS is pretty clever, although purely syntax wise I like this the least. It feels a lot like these old C functions for getting an error code.
Although the trick is really clever, perhaps something like the fragment below would be more closer to what Java developers expect?
Select select = select(words); for (Select each: select) { each.yield = each.value.charAt(0).isUppercase(); } Collection names = select.result;December 6th, 2008 at 11:35
Adrian,
I think it is a clever approach to a thorny issue. Like it a lot. I bet you will get criticism from FP/Rub/Smalltalk people saying that Java sucks and that it should have supported this and that. This may be true, but still you managed to find a good practical solution to a real problem.
December 9th, 2008 at 18:14
hehe, nice idea :)
December 17th, 2008 at 23:37
Awesome idea - Impressive enough to warrant an RSS and Follow on Twitter.
I saw this attempted here as well, but you’re limited to using the Hamcrest matchers: http://code.google.com/p/hamcrest-collections/wiki/GettingStarted
February 11th, 2009 at 22:20
@henk – now it can be done, checkout the latest revision.
Select<String> selection = Select.from( words ); for (Select<String> each: selection) { each.yield = each.element.charAt(0).isUppercase(); } Collection<String> names = selection.result();February 20th, 2009 at 19:16
I definitely support such kind of experiments!
But just compare
Select selection = Select.from( words ); for (Select each: selection) { each.yield = each.element.charAt(0).isUppercase(); } Collection names = selection.result();Vs
Collection names = new ArrayList(); for (String word : words) { if (Character.isUpperCase(word.charAt(0))) names.add(word); }Idiomatic java looks not that bad in comparison
February 27th, 2009 at 03:35
@Eugene – I agree, idiomatic java does a good job for simple queries. It is when queries get complex that the DSL starts to pay off. That is, either for queries such as GroupedBy and PiecesCut with a complex internal logic, or for query whose loop body is more than a few lines long.
A main strength of the DSL is that it is intention revealing. “Say what you do, not how you do it.” Scanning code for uses of Select is much faster then deciphering an idiomatic construct. Even worse, any slight variation in the combination of for’s and if’s might turn your select idiom into a reject, or select or detect instead. Thus, I personally prefer the DSL for trivial cases even.
And now, installing lambda4jdt … looks wow!