Sunday, May 24, 2009

How it Works: Groovy Default Parameter Values

Finish this sentence:

PHP is a better language than Java because it has _________.

Ouch. It's like a bad question from "Would you rather"[1].

The first language feature that comes to my mind is default parameter values. Why doesn't Java have this? And it doesn't even seem to be on the Project Coin list either. Is this so hard?

void greet(String target = "World") {
System.out.println("Hello " + target);
}
Groovy, of course, has default parameter values. This does work in Groovy:
def greet(target = "World") {
println "Hello $target"
}
assert greet() == "Hello World"
assert greet("Universe") == "Hello Universe"
Not the sexiest feature, but admit it... you're dieing to know how this works under the covers, right? Who's with me? Who wants to see some AST?

The Groovy compiler has several phases, and one of the early phases is Conversion. Take a look at what AST the above method declaration gets transformed into during Conversion:

First off, this is a screenshot from Groovy 1.7's AST Browser, which is a new part of the GroovyConsole. Just type some script into the console and press Ctrl+T (or open Apple or whatever) to see the AST. Groovy compiles scripts into Script classes, so that is the ClassNode named "script1243135883750" (the number is the system time, btw). The new greet(String) MethodNode hangs off the ClassNode, it has one parameter named "target", "target" has a default value of the "World" ConstantExpression, and the method body is wrapped in a BlockStatement. Look at the screenshot again... you can easily glean all this information from it.

Now check out how the ClassNode looks in the later phase Class Generation:

Wowee! Where did all those methods come from? getProperty(), evaluate(), getMetaClass()... all these methods are added to the class between Conversion and Class Generation. But check out the names of the two MethodNodes that are expanded in the screenshot. They're both "greet" right? Except the first greet takes a String parameter and the second greet takes no parameters. The second one just has a BlockStatement underneath it with no Parameter node. The expanded contents of the BlockStatement shouldn't be a surprise:

The new greet() method consists of nothing but a return statement and a method call to this.greet(String) with the ConstantExpression "World" as a parameter. How this works seems sort of obvious in retrospect: the compiler synthesizes a new version of greet() that is overloaded with one less parameter, and then hardcodes the default parameter value into the method body. This is probably the code you would have written to overload the method yourself.

The nice part of this implementation is that it's written to the class file and resolvable from Java. So Java classes can use your default method parameters as well. It just looks like a normal overloaded method. You can verify this by running the Groovy generated class file through the standard javap program.
groovyc MyScript.groovy
javap MyScript

Compiled from "MyScript.groovy"
public class MyScript extends groovy.lang.Script{
...
public java.lang.Object greet(java.lang.Object);
public java.lang.Object greet();
...
}
In the introduction to "Clean Code", Ward Cunningham has this to say: "You know you are working on clean code when each routine you read turns out to be pretty much what you expected." Perhaps this extends to implementations of all sorts: you know you're working with a clean implementation when the explanation of it turns out to be pretty much what you expected. So nothing to see here, move on.

[1] Before you leave a comment about PHP, remember bashing PHP is totally not the point of this post. While it may not be my first choice of language, I do happily have PHP code running in production somewhere on the Internet right now. For some reason PHP is just always the language community's whipping boy for some reason.

3 comments:

Anonymous said...

Interesting post, I like to see this kind of detail and have used javap for this kind of thing in the pass. I'll have to give the AST browser a try in these situations, thanks.

That also explains why you can only give default parameters to the final arguments in a method and not the first arguments.

Funny that you should mention Clean Code too. I just reread a big chunk of it over the weekend and have been thinking about the section related to method naming and function parameters.

After reading that section, I think I'm less likely to actually use default parameters than I have been in the past.

I'd be more likely to create an explicit method (greetWorld()), especially in a public API.

(I know the point of your article was to show how the AST browser is a good tool for peeking into class files and not the use of default parameters, don't mean to derail :)

Hamlet D'Arcy said...

I use default parameters a lot in my test tree, but use it rarely in my source tree. So I think I agree with you. I think production source meant to be reused by others benefits from being explicit about things, while it's not as important in the tests.

木須炒餅Jerry said...
This comment has been removed by a blog administrator.