Roshan has posted 7 posts at DZone. View Full User Profile

Groovy.compareTo(Groovy++) - Part 1

02.07.2010
| 12699 views |
  • submit to reddit

My previous article A sneak peek into Groovy++ covered what Groovy++ is, what are the pros and cons of using it, where it fits compared to Groovy and Java and some high level differences between them. The purpose of this article is to go a bit deeper and try out a few more basic examples and see how things differ in various areas.

The idea here is not to suggest one or the other, but just to bring out the differences for our information and to see if / how the differences can be taken care of.

So, let's begin with some examples right away.

1.  Stricter compile time checks 

If we take the following piece of code:

/* Leave it commented to run the dynamic Groovy version; 
Uncomment to run Groovy++ version */
//@Typed
package test

def x = { List list ->
list.size()
}

x(1)

Groovy's not-so-strict type checking allows the above code to get compiled successfully. It fails with a runtime exception because we are invoking x(Integer), where only x(List) is defined. It relies on you to cover such things in your thorough unit testing.

With Groovy++, we get a compile time error - "Cannot find method { List -> ...}.doCall(int)", indicating that it couldn't invoke with arguments (int) on the closure {List ->}

The flip side of the compile time checks is that we lose on the duck-typing.

So, if we have:

/* Commented -> dynamic Groovy version; 
Uncommented -> Groovy++ version */
//@Typed
package test

class Foo {
def greet() {println "Foo says hello"}
}

class Bar {
def greet() {println "Bar says hello"}
}

def c = {greeter -> greeter.greet()}

c(new Foo())
c(new Bar())

With Groovy, we get the benefits of duck-typing. Even though, there is no relation between the types Foo and Bar, we still are able to invoke methods that they respond to.

With Groovy++, we instead get a compile time error "Cannot find method Object.greet()" because it tries to go by the static type of the closure parameter.

2.  On-the-fly type modifications through ExpandoMetaClass

In the following example, we try to add a method to the String class using the ExpandoMetaClass feature.

/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test

String.metaClass.foo = { -> println "foo called" }
"".foo() /* call my new method */
With Groovy, the code runs fine and makes the dynamically-added method foo() available on the class java.lang.String.

Although Groovy++ does not support this feature in its fully static mode, it offers a "Mixed" mode for scenarios such as these, where it tries to provide the best of the both worlds.

Here is a little preview of Groovy++'s Mixed mode:

@Typed(TypePolicy.MIXED)
package test

String.metaClass.foo = { -> println "foo called" } // supported

"".foo()

class A {}
class B {}

A a = new A()
B b = a // this assignment produces a compile time error

What the above code shows is that, when you want, you can use the Mixed mode to get the best of the both worlds - Groovy's dynamic features as well as static type checking.

3.  Closures - More like Java's inner classes now

In Java, inner classes cannot refer to the non-final members of its outer scope. For example, the following fails to compile in Java:

void foo(){
String data = "";
class Inner {
void innerFoo() {
System.out.println(data);
}
}
}

Groovy treats closures as inner classes, but places no such restrictions on the access to the non-final members. So, the following code runs fine in Groovy:

void foo(){
String data = 'original';
def cl = {data = 'changed'} // access non-final data of its outer scope
cl()
assert data == 'changed'
}

foo()

Groovy++ comes closer to Java here. It only allows read-only access the non-final data members. If you try to make modifications, you get compilation errors. So the following code fails with the message "Cannot modify final field test.Test$foo$1.data"

@Typed
package test

void foo(){
String data = 'original';
def cl = {data = 'changed'}
}

foo()

Here is what Groovy++ project lead Alex Tkachman has to say about it: "Yes, outer variables are final and it is by design - one of main usecases for Groovy++ is concurrent programming and non-final variables are suicide in this situation."

But what if you are not doing concurrent programming with Groovy++, but, say, you are migrating some Groovy code to Groovy++ that modifies the data from the outer scope in its closures?

Is there a solution Groovy++ offers? - Yes, you can explicitly use the technique Groovy uses behind the scenes and wrap the data you want to modify in groovy.lang.Reference object, as shown in the code below:

@Typed
package test

void foo(){
Reference data = ['original']
def cl = {data = 'changed'} // now even Groovy++ supports modification of non-final outer scope data
cl()
assert data == 'changed'
}

foo()

4.  No direct access to private members anymore

Groovy does not restrict access to a class' private members from outside. So, the following code goes through:

/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test

class SecretService {
String secret = "secret"
String getSecret() {
"covered-" + secret
}

private launchOperation() {
"launched"
}
}

def ss = new SecretService()

assert ss.@secret == "secret" // can access the private field directly
assert ss.launchOperation() == "launched" // can access the private method

Groovy++ restricts such private members access at compile time. In the same example as above in groovy++, "ss.@secret" results in "Cannot access field test.SecretService.secret", and "ss.launchOperation()" results in "Cannot access method SecretService.launchOperation()"

5.  A few more minor differences

     a)  Script binding variables
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test

foo = "foo"

assert foo == "foo"

The above code runs fine in Groovy - it defines the variable "foo" in the script's binding. In Groovy++, this is not supported. If you want to use this feature in Groovy++, use @Typed(TypePolicy.MIXED).

    b)  Property style access for maps
/* Commented -> dynamic Groovy version; Uncommented -> Groovy++ version */
//@Typed
package test

def map = [key1: "value1"]

assert map.key1 == "value1"

Groovy supports property style access of map data, as in the example above. Groovy++ does not support it, unless you use MIXED mode.

The End - Part 1

The examples above cover a few things I have been able to figure out about the differences between Groovy and Groovy++ till now. I intend the play more with Groovy++ and try to uncover a more differences. I encourage you to do the same and share your findings.

References:

    1) Project Groovy - http://groovy.codehaus.org/

     2) Project Groovy++ - http://groups.google.com/group/groovyplusplus and http://code.google.com/p/groovypptest

Published at DZone with permission of its author, Roshan Dawrani.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Reiner Saddey replied on Mon, 2010/02/08 - 3:16am

Hi Roshan, just awesome, can't wait till it makes its way to Grails. It has always bothered me that Groovy just ignores type information, even when it is readily available (forces you to be constantly looking for the underlines in IntelliJ). Thank you for this slick introduction

Reiner Saddey replied on Mon, 2010/02/08 - 4:28am

Re: Inner classes (i.e. classes within methods): The reason for Java requiring the final attribute when accessing outer scope local variables is that they will not be accessed directly, but via local copies instead and it would be confusing for mutations not to be visible outside of the inner scope. Why is the inner scope not allowed to access the outer one directly? The answer is quite amazing: Because it may no longer exist. Image the outer (method) scope returning a reference to a class that accesses variables local to the outer (method) scope. Those variables live within the call stack and will cease to exist, once the method call returns. How can Groovy access values that no longer exist? I'm baffeled...

Osvaldo Doederlein replied on Mon, 2010/02/08 - 5:59am in response to: Reiner Saddey

Let me clarify this: there is NO problem in closure-capture of non-final variables. Languages that support that will simply "lift" the variable from the method stack to the heap (inside the closure object). Illustrating,

int data = 1;
++data;
def c1 = { data *= 10; }
c1();
println(data); // prints 20

gets compiled to something like:

class $closure {
public int data;
public void run () { data *= 10; }
}

$closure $$c = new $closure();
$$c.data = 1; // translation of "int data = 1"
++$closure.data; // translation of "++data"
def c1 = $$c; // translation of "def c1 = { data *= 20; }"
$$c.run(); // translation of "c1()"
println(data); // still prints 20

The sample translation above is just an example that might raise some "what if..." questions, but it's just to illustrate the point; actual translation may sometimes be more complex to address fine details (e.g. definite assignment rules) and corner cases.

This technique should be familiar to whoever looked at the code emitted by Java's inner classes. But inner classes make a copy of a local variable into their fields; for non-final closures we just need a small change - the variable is not copied, it is totally moved to the helper class, so any access to that variable in the original method must now go through the extra indirection of the closure object. This creates an overhead, OTOH is surely beats the hell out of not having this feature at all, and the overhead is lower than what you pay for doing it manually, e.g. with the proposed use of a Reference wrapper object (or a single-element array, common practice in Java) because now you need at least one extra heap-allocated object (other than the closure itself), and you may need several if the closure will capture multiple non-final vars. Wioth the smarter closure design that captures non-finals, you only need a single object (the closure). [When the closure is only "consumed" internally in the same method that defines it, all solutions are similar for a modern JVM that can do Escape Analysis-based Scalar Replacement. But this is not typical usage of closures, except in bad microbenchmarks.]

In short, this design decision of Groovy++ seems borked IMHO.

Reiner Saddey replied on Tue, 2010/02/09 - 4:21am

Thank you Osvaldo,
your explanation may have uncovered some arrogance of mine and definitely changed my attitude towards Groovy compilers - long way since leaving the Java coffee pot and me doing compiler work :-)
Take care, Reiner

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.