Fork me on GitHub

21 Nov 2009

Why do Strings behave like a Collection?

Mostly I love Groovy but every now and then some behaviour gets on my nerves. For example, why do the iterator methods on String behave as though it was a collections of characters rather than an Object? If I define a method with a dynamic-typed parameter like:
void printNames(names) {
    names.each {
        println it
    }
}
Passing a collection of Strings results in each String being printed. Passing a single String results in each character of the String being printed separately. I would have thought that the Object behaviour was a more appropriate default here (iterators on Object are called once passing the object itself). After all the .chars property is available if you really want to represent a String as a collection of characters.

The solution is to override the method:
void printNames(String name) {
    printNames([name])
}
Unlike in Java dispatch to such overridden methods in Groovy is based on the runtime rather than declared type of the parameter.

You might think this is a trivial point, but the scenario that keeps biting me is HTTP parameters passed into a Grails controller. If you have multiple inputs with the same name, or a multi-select then sometimes params.x is a String and sometimes it's a List of Strings. Any code dealing with that parameter has to handle the two cases separately.

5 comments:

Robert Elliot said...

Groovy frequently irritates me, but on this one I'd defend them - it makes perfect sense to me that a String should be treated as a list of characters, indeed I've often thought it's an oddity in Java that String doesn't actually implement List<Character>. After all that's what it is.

The problem seems to me to be more with the Grails params object - it's distinctly odd behaviour really to have a Map containing a mixture of Strings and String arrays. It would of course be irritating if the params object only contained String arrays (as is of course the case in ServletRequest.getParameterMap()) as 90% of the time there would only be one element in each array, but it would mean you could reliably treat each element in the map in the same way.

Perhaps Grails should have two maps - one containing String values and the other containing String array values, on the basis that the calling code will know whether it's expecting exactly one value or not for that param name?

Rob said...

My point is that 99% of the time in application programming you don't *care* that a string is a collection of characters, it would be more useful to treat it as an atomic object. Having to use instanceof kind of defeats the point of duck typing.

Robert Elliot said...

True, but I think what I'm suggesting is that this is merely the symptom that's pointing out an underlying problem; make String not a Collection and the problem can still rear its head the next time a similar method is written that could take a Collection of Collections or just one of the bottom level Collections.

It seems to me more like an example of the inherent fragility of duck typing than a problem with String's implementation in Groovy.

snaglepus said...

I think I have to agree - I have been caught out with this one a couple of times. Where I have finally realized what it was doing. And then you need to write conditional code to determine if its a string or a map that is passed. Annoying!

Graeme Rocher said...

how about a method on the params object like:

params.list("myParam")

So that you always get a collection for "myParam"