Edit: This fix has been checked into the evolving 1.6 version, and the developers are discussing if it will be in the final 1.5.x release.
Let’s start with the punchline:
def x = [ [a:1, b:2, c:3] ] def y = x.flatten() assertEquals([1, 2, 3], y)
So, flatten, whose JavaDoc says that it flattens containing collections, also extracts the views from maps and flattens those. This is odd. It wouldn’t have been a big deal, except that I regularly use maps as “pseudobeans”.
Consider this case:
def fetchX(inBean) { inBean.props.x(SOME_CONST) }
If I were to unit test that, I’d generally do something like this:
def arg = null def inBean = [ props : [ x : { it -> arg = it; 5 } ] ] def out = fetchX(inBean) assertEquals(5, out) assertEquals(SOME_CONST, arg)
What I’m doing is mocking out the property access by leveraging Groovy’s dot-accessor for maps. Since property accessors for beans and the dot-accessor for maps are the same syntax, I can use them interchangeably. I’m also faking a method by adding a closure to a map, since calling a closure from a map via the dot accessor looks exactly like a method call. And, just for fun, I captured the method argument via a closure. There’s more on this stunt here.
Unfortunately, the difference between using JavaBeans and maps becomes readily apparent if you use #flatten, because it mangles maps. This is sad, and resulted in the hackiness of re-writing list#flatten in my application.
Other issues with list#flatten at the moment:
- It doesn’t flatten arrays.
- It causes a stack overflow if a list contains itself.
I submitted a patch to fix the map issue, and someone else enhanced the patch to handle arrays. I’m going to update the stack overflow issue later today, when I’m done working.
Here are two tickets where you can learn more:
GROOVY-2903
GROOVY-2904
2 Comments
There’s a wonderfull concept from Neal Stephenson’s “In the Beginning was the Command Line” of Metaphor Shear. He was talking about GUIs- how suddenly you discover that your word processor isn’t a typewriter, for example- but it applies here. You had a metaphor that you could treat a map like an object, and it worked really great- right up to the point where it didn’t.
This is why I’m very suspicious of things masquerading as other things. I’m not talking about data abstraction layers here, for example hiding a map inside some other object. No, I’m talking about maps pretending to be objects, lists pretending to be arrays, etc. There isn’t a problem saying “this isn’t a map, this is a foo”, because there’s no preconceived notion of what a foo is, or how it behaves. But trying to say “this isn’t a map, it’s a list!” tends to lead to tears and recriminations.
The Liskov substitution principle applies- “is a” relationships are do or do not, there is no try. And your language is doing you no favors giving you an “almost is a” relationship.
Given the preponderance of bean structures in Java, I’m actually a fan of the ability to drop maps in and just move on with life: it’s very similar to Perl’s old solution, and OCaml’s records.
The problem is solved if maps are left unadulterated by list#flatten, so that’s what we’ve gone for. I’m not sure who originally decided that excising values and flattening those — nobody on the Groovy developer list has fessed up to it or come to the defense of that implementation.
One Trackback
[...] leaves just the more thorough blog posts on topics (like Groovy’s list#flatten and the status of Ruby’s libxml) and reasonably significant announcements (like presentations [...]