Entrepreneur. Creator of Groovy++ Alex is a DZone Zone Leader and has posted 31 posts at DZone. You can read more from them at their website. View Full User Profile

Groovy++ in action: statically typed dynamic dispatch

10.27.2010
| 14464 views |
  • submit to reddit

It already became common place to say that both dynamically and statically typed code have their own merits and drawbacks. Groovy builders (xml, markup or whatever) are huge example where dynamic dispatch wins big time compare to statically typed code in terms of expressiveness (except that it is not easy to mix typed and untyped code together) but lose in performance and static checks. Today I am going to show how we can build statically typed builder with Groovy++ without paying such penalties.

Groovy++ already contains unique feature to combine statically and dynamically typed code together. If you annotate your method (or class or whole script) with @Typed(TypePolicy.MIXED) then compiler will not complain about unresolved methods and properties but instead will generate dynamic invocations, which will be dispatch via meta class.

That still does not solve problems of type checking and performance. Even worse it might introduce new runtime bugs as we under impression that we are protected by compiler, so in this article we present different and better approach.

Let us start with example, which is inspired by Groovy Enhancement Proposal 7 - JSON Support.

We want to build and print in to standard output JSON representation of personal data of a person.

JsonClosure externalData = {
additionalData {
married true
conferences(['JavaOne', 'Gr8conf'])
projectRoles([
'Groovy' : 'Despot',
'Grails' : 'Commiter',
'Gaelyk' : 'Lead'
])
}

whaeverElseData (["xxxxxx", [x: 12, y:14], ['a', 'b', 'c']])
}

JsonBuilder2 builder = [new PrintWriter(System.out)]
builder.person {
firstName 'Guillaume'
lastName 'Laforge'
address (
city: 'Paris',
country: 'France',
zip: 12345,
)

externalData ()
}

And here is expected JSON output

{
"person" : {
"firstName" : "Guillaume",
"lastName" : "Laforge",
"address" : {
"city" : "Paris",
"country" : "France",
"zip" : 12345
},
"additionalData" : {
"married" : true,
"conferences" : [ "JavaOne", "Gr8conf" ],
"projectRoles" : {
"Groovy" : "Despot",
"Grails" : "Commiter",
"Gaelyk" : "Lead"
}
},
"whaeverElseData" : [ "xxxxxx", {
"x" : 12,
"y" : 14
}, [ "a", "b", "c" ] ]
}
}

Now we come to the most interesting question: how can such dynamic behavior be implemented in statically compiled language.

This is surprisingly simple: when compiler finds the method call, which it can not resolve, it starts looking for a method with special name "invokeUnresolvedMethod" which has absolutely any return type and absolutely any parameters as long as first parameter is of type String.

If the method not found in current class it check outer one (of course, if such outer class exists). That allows to use this technique in nested closures.

If such a method found compiler will try to generate code to invoke it. If not, it will provide compile time error.

The main beauty of this approach is that we can be as specific as we want about parameters and return types, so all power of type inference and type checking is at our disposal.

Let us see how it works. Our implementation of JsonBuilder is surprisingly simple

class JsonBuilder2 {
protected static ThreadLocal current = []

private final MappingJsonFactory factory = []
private final JsonGenerator gen

JsonBuilder2(Writer out) {
gen = factory.createJsonGenerator(out)
gen.useDefaultPrettyPrinter()
}

void call(JsonClosure obj) {
try {
current.set(gen)

gen.writeStartObject()
obj ()
gen.writeEndObject()
gen.close ()
}
finally {
current.remove()
}
}

void invokeUnresolvedMethod(String name, JsonClosure obj) {
call {
gen.writeObjectFieldStart name
obj ()
gen.writeEndObject()
}
}
}

Underneath we use brilliant Jackson framework for handling JSON.

The one, who is not familiar with Groovy/Groovy++, may note how easy to reuse existing Java libraries.

Our builder is very simplified, so we have only two forms of using it. The one is simplified as we used above and another one is more generic. We can rewrite example above in more generaic foram as

builder = [new PrintWriter(System.out)]
builder {
person {
firstName 'Guillaume'
lastName 'Laforge'
address (
city: 'Paris',
country: 'France',
zip: 12345,
)

externalData ()
}
}

It is very important to notice that there is huge difference between first and second form.

builder.person{...} means builder.invokeUnresolvedMethod("person", (JsonClosure){...}) and

builder {...} means builder.call((JsonClosure){...})

Now to complete our story we need to implement JsonClosure. It is a very staright forward and I will omit part of the code below. The whole code can be found at Groovy++ repository

abstract class JsonClosure {

protected JsonGenerator gen

abstract void define ()

void call () {
if(!gen)
gen = JsonBuilder2.current.get()

if(!gen)
throw new IllegalStateException("Can't use JsonClosure outside of JsonBuilder")

define ()
}

void invokeUnresolvedMethod(String name, Object obj) {
if(obj == null) {
gen.writeNullField name
return
}

switch(obj) {
case Closure:
gen.writeObjectFieldStart(name)
obj.call()
gen.writeEndObject()
break

case JsonClosure:
gen.writeObjectFieldStart(name)
obj.gen = gen
obj.define()
obj.gen = null
gen.writeEndObject()
break

case String:
gen.writeStringField(name, obj)
break

case Number:
gen.writeNumberField(name, obj)
break

case Map:
gen.writeObjectFieldStart(name)
for(e in obj.entrySet()) {
invokeUnresolvedMethod(e.key.toString(), e.value)
}
gen.writeEndObject()
break

case Iterable:
gen.writeArrayFieldStart(name)
iterate(obj)
gen.writeEndArray()
break

case Object []:
invokeUnresolvedMethod(name, obj.iterator())
break

case Boolean:
gen.writeBooleanField(name, obj)
break

default:
gen.writeObjectField(name, obj)
break
}
}

void iterate(Iterable obj) { ........... }
}

That's almost it. Of course, more involved builders can also use getUnresolvedProperty(String) and setUnresolvedProperty(String,SomeType) which allows to translate unresolved property access in to fast dynamic calls (This feature is not yet implemented in the trunk)

I hope it was interesting and maybe for someone open new view angle on dynamic dispatch.

Thank you for reading and till next time.

Published at DZone with permission of its author, Alex Tkachman.

Comments

Marko Milicevic replied on Wed, 2010/10/27 - 1:54pm

Cool.

>> The main beauty of this approach is that we can be as specific as we want about parameters and return types, so all power of type inference and type checking is at our disposal.

Is this point demonstrated in your example? If not, could you provide one?

Thanks.

Vadim Voituk replied on Thu, 2010/11/04 - 3:26am

when compiler finds the method call, which it can not resolve, it starts looking for a method with special name "invokeUnresolvedMethod"
In this case we're loosing the dynamism provided by MetaClass. Is't it?

Alex Tkachman replied on Tue, 2010/11/16 - 6:22am in response to: Vadim Voituk

In G++ mixed mode you have both.

Nurul Choudhury replied on Sat, 2011/11/12 - 6:51am

Alex, very interesting article, and I am amazed how well you and your team have combined the expressiveness of Groovy and safety and performance of static typing. I really commend you for your hard work in making it happen.

SORRY ABOUT THE LONG COMMENT

The switch on type issue

But you have nailed the issue of mixing that there are some places dynamic behavior is absolutely required. The solution in Groovy++ is really the the same strategy taken in Java the ugly switch/if-else statement listing "if object instanceof Class1 then preform-1, Class2 perform-2..." this is ugly but worse error prone, if you have more than one place this has to be done. In that case the compiler is of no help making sure that you have changed all the places.

We need some way of telling the compiler that "Object x" represents on of the following set if classes "Class1, Class2 ...", and when "method1(x)" at compile time check if there are "method1" variants for Class1, Class2... and essentially write the switch statement for you. The compiler can also warn you that there not typed variants of "method1" that 'x' may be, and it can also throw an exception if 'x' is not one of the required types.

I have not fully thought this through, but the compiler under the actually create an hidden method called say '$dynamic_convert' that encapsulates the switch statement so that the same can be used in multiple places, and secondly support inheritance.

example: note: below the meaning of the annotation @Typeset is that NUMERIC is s pseudo type that may of type Integer, Double, BigDecimal or any of the types listed in BASE_TYPESET. The new NUMERIC can be use anywhere a Class is used and the effective result of this is that the compiler now knows how to generate the switch.



Class Extended extends Base {
 ...
@Typeset(NUMERIC, BASE_TYPESET, Integer, Double, BigDecimal, List)

    String service(NUMERIC x) { 
       
        if(..) {
           def v = super.service(x) // use the method $dynamic in the superclass and make sure
                                                // x id of type in (BASE_TYPESET) 
        }
        ....    
               y = convert(x)
       ...
    }

    void service2(NUMERIC z) {
               y1 = convert(z)
       ...         
    } 

   String convert(NUMERIC<Integer> v) { ... }
    String convert(NUMERIC<List<Integer>> v) { ... }
    String convert(NUMERIC<Double> v) { ... }
    // <<< compiler warning convert() not defined for BigDecimal
}

in the case above, the compiler generates a hidden method called
  String $dynamic_convert(Object x) {
      switch(x) {
        case Integer:  return convert((Integer)x)
        case Double:   return convert((Double)x)
        ...
        default: return super.$dynamic_action(x)
      } 

Now for the base class

@Typeset(BASE_TYPESET, String, BigInteger)

class Base {
...
      String service1(INTEGER x) {
                ....
                return convert(x);   // actually invokes $dynamic_convert(x)
       }

    String convert(INTEGER<BigInteger> v) { ... }
    String convert(NUMERIC<String> v) { ... }

// generated by the compiler 
  String $dynamic_convert(Object x) {
      switch(x) {
        case Integer:  return convert((Integer)x)
        case Double:   return convert((Double)x)
        ...
        default: return super.$dynamic_action(x)
        default: throw new InvalidTypeException(....)
      } 
 }

Nurul Choudhury replied on Sat, 2011/11/12 - 7:01am in response to: Nurul Choudhury

The mechanism becomes even more useful if you have @Typeset as a parameter for a method, then the self generated switch statement becomes big and unweildy, since you have to have to cross multiply the sets.

@Typeset(X, ClassX1, ClassX2, ClassX3)
@Typeset(Y, ClassY1, ClassY2)

...
     case ClassX1:
                           switch(..) {
                               case Y1: return    convert((X1)x, (Y1)y);
                               case Y2: return    convert((X1)x, (Y2)y);
                              ...
                           }
     case ClassX2: ...

...

Comment viewing options

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