Tuesday, August 25, 2009

An Understated Feature in Groovy

Background

I have worked with Groovy for awhile, but have been averse to one feature: the implicit return of the last expression evaluated. For example:


def getFoo() {
def foo = null

if (something) {
foo = new Foo()
}

foo // no 'return' necessary
}

In the above code, I would use return because it felt safer to me. I tended to agree with Eric's post (with respect to explicit returns).

Revelation

I get it now, and it's all thanks to the enhanced collection methods in Groovy.

Consider the collect method. It transforms a list by applying a function. In pseudocode:


[ a, b, c, d] => [ f(a), f(b), f(c), f(d) ]

With that in mind, here's another example:


class Composer {
def name
// def era, etc
}

def list = [ 'Bach', 'Beethoven', 'Brahms' ]

// the closure returns the new Composer object, as it is the last
// expression evaluated.

def composers = list.collect { item -> new Composer(name : item) }

assert 'Bach' == composers[0].name

The idea is simply to build a list of Composer objects from a list of strings. As noted in the comment, the closure passed to collect uses the implicit return to great effect. At one time, I would have taken 3-4 lines to express that idea.

But now, this code is not merely concise: it is truly elegant. Very reminiscent of other languages such as Python.

The Take-Home Message

There are many lists of excellent Groovy features. However we rarely see 'implicit return' listed. I'm a fan: it greases the wheels for other features.

I realize it is available in many languages: I just haven't used it in Groovy. I suspect in a few weeks I won't be able to live without it.

4 comments:

phil varner said...

So, would you write the function now as such?

def getFoo() {
something ? new Foo() : null
}

Unknown said...

I've evolved some personal guidelines from experience. The first three are equally applicable to most of Groovy's optional/implicit syntax.

1) If there is any doubt about ambiguity (either technically or in human interpretation) or about whether the implicit behavior would be immediately obvious to another developer, make it explicit. Clarity and simplicity are fundamental!

For example, if the last code block in a method is an if/else, definitely use explicit returns (preferably as a clear single point of exit after the block). Rather than being noise, it serves the purpose of clearly communicating the return value(s).

2) If there is no doubt that being explicit just adds noise, don't do it. Noise is complexity. Clarity and simplicity are fundamental!

For example, the following is very common in Grails domain classes, and is very idiomatic Groovy.

String toString() {name}

This also demonstrates breaking with typical Java style conventions for the placement of curly braces. Some believe consistency throughout a project is the most important factor in style conventions. But the terseness of Groovy suggests that context is more important to style decisions. The one-liner above would become unnecessarily noisy as a three or four-liner.

"A foolish consistency is the hobgoblin of little minds" ~Emerson


3) If there's a choice between two options with otherwise equal merit, select the one that is more idiomatic to Java.

What heresy is this?! On the contrary, it's a very practical guideline, and practicality is idiomatic to Groovy. Most Groovy developers will be coming from a Java background, and some will be new to Groovy. That's relevant context, because...clarity and simplicity are fundamental!

4) The purpose of every line of code should be as clear as possible. So if the last line contains nothing of value other than to specify the return value, then be explicit.

Mike, regarding your example of the dangling "foo", I agree completely. It does nothing but specify the return, so why not write it as "return foo" so that the purpose of the line is self-documenting? Without an easily discernable purpose, it can scan as an accident. "Better flag that one in the code review, looks like Mike did a cut&paste and left some litter behind." (This shows that guideline 4 is really an instance of guideline 1, but for clarity, it is worth covering explicitly.)

I should mention that like the closure examples you cited, explicit return statements are non-idiomatic in builders and Grails controllers. Since builders are essentially DSLs that should look more like node trees, and less like code, this guideline doesn't apply. (This is the third example, after the terse toString() in 2, and guideline 3, that demonstrates a sub-clause in our mantra: clarity and simplicity are contextual!) In Grails controllers, in the most typical case of returning a model as a map of names to objects, the final line does have its own clear purpose--building the map. So this is not like a dangling foo.

In honor of Code To Joy idioms, perhaps this principle should be titled:

"Never return a lonely foo."

Ouch, that was fail.

foo

Michael Easter said...

Dear Foo Fighters,

Thanks for the notes, esp. David for the thoughtful reply.

re: getFoo(). That example was intended to showcase the implicit return for potential Groovy newbies. It was a setup for the real meat (the collect method).

That said, I generally have hated the ternary operator, though I am bending somewhat in Groovy.

re: guidelines. No one can deny the virtues of clarity and simplicity. Where things get sticky though, are one's assumptions about what someone else perceives as clear and simple ;-)

re: heresy. Very interesting idea, and it makes sense to me.

re: CodeToJoy idioms. Well-said. I have a retort, but "I won't get foo'ed again!" ;-)

Eleanor said...

Pretty worthwhile data, lots of thanks for your post.