Agile Zone is brought to you in partnership with:

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

How statically typed meta programming can look

11.16.2010
| 11958 views |
  • submit to reddit
I want to start with piece of code and challenge the reader to understand what the code means. To make the task a bit fair, I am telling you that this is the full content of the file called .../grails-app/controllers/gppgrailstest/WebSocketChatController.groovy, which is obviously part of some Grails application.
def chatService

index: {
def id = request.session.id
[
sessionId: id,
userName: request.session.userName ?: (request.session.userName = chatService.newUserName())
]
}

OK, it is really hard to keep the intrigue for a long time and the code below is an exact copy of the script above after some AST transformations, which is Grails-aware.

If you are not aware about Groovy AST transformation you must learn it immediately. It is extremely powerful technique, which allows you to write compiler plugins in order to have very expressive domain specific languages. The general idea is that AST (abstract syntax tree) transformation modifies internal representation of your code on different stages of compilation.

package gppgrailstest

@Typed class WebSocketChatController implements org.mbte.grails.languages.ControllerMethods {
gppgrailstest.ChatService chatService

static def defaultAction="index"

Closure index = {
def id = request.session.id
[
sessionId: id,
userName: request.session.userName ?: (request.session.userName = chatService.newUserName())
]
}
}

On the first look, our transformation did not do anything non-trivial, but it did a lot. To be precise:

  • we added package declaration by deducting package name from Grails convention
  • we transformed the original script into controller class
  • declaration of script top level local variable 'chatService' became property 'chatService'
  • we found that our Grails application contains service bean called chatService of type gppgrailstest.ChatService and understood that property 'chatService' will be injected with this bean
  • we transformed labeled block expression in to property index of type Closure
  • we realized that as we have only one action in our controller it will be default one
  • we added annotation @Typed to our controller class instructing that it must be compiled statically
  • we added trait interface org.mbte.grails.languages.ControllerMethods to our controller

I can agree that AST transformation itself was not too complicated. In fact the full code for the transform is less than 110 lines of Groovy++. What is far from trivial is the fact that our code is really can be statically compiled.

There are at least two hard questions compiler need to resolve to be able to compile it:

  • what do we mean by property 'request'
  • even if we understood that 'request' is of type HttpServletRequest and 'request.session' is of type HttpSession and 'request.session.id' is of type String, it is still not clear what 'request.session.userName' means

The second question is a little bit easier, so we will start with it. In out previous article Groovy++ in action: statically typed dynamic dispatch we discussed powerful tecnique of handling unresolved methods and properties by the Groovy++ compiler. All we need to do is to add methods getUnresolvedProperty and setUnresolvedProperty to our class.

If HttpSession was class (in oppose to interface) and we could modify this class (which we can not any way) then all we need to do is to add two following methods.

    def getUnresolvedProperty(String name) {
getAttribute(name)
}

void setUnresolvedProperty(HttpSession session, String name, Object value) {
setAttribute(name, value)
}

Because HttpSession is interface we need to use extension (or category) methods

    static def getUnresolvedProperty(HttpSession session, String name) {
session.getAttribute(name)
}

static void setUnresolvedProperty(HttpSession session, String name, Object value) {
session.setAttribute(name, value)
}

Extension or category methods are static methods defining additional methods for some other type. The class to be extended is defined by the type of the first parameter and the parameters of added methods are defined by the rest of the parameters.

There are three ways in Groovy++ to make extension methods applicable during compilation

  • @Use annotation
  • static methods defined in compiled class or it's superclasses
  • globally by specially named registration file in the  class path
Groovy++ provides many extension methods for different servlet related classes.

So we are done with 'request.session.userName' and by using statically typed dynamic dispatch and extension methods it works now as a session attribute.

What about the 'request' property and all the other methods and properties which Grails usually provides to the controller via dynamic runtime meta programming? We need to make all these methods available in the controller class (and then in action closure). There are several ways to achive that

  1. We can try to introduce a common super class for all controllers and derive our controller from this super class. This is not so good if we want to reuse some controllers by inheritance.
  2. We can introduce an empty marker interface and define some extension methods for this interface as we did before. This is better, but we can not override the method in our controller.
  3. We can use traits.
The trait is Groovy++ (the concept is borrowed from Scala) and it is an interface with default implementations of some methods.

If a statically compiled class implements such an interface but does not provide its own implementation of such a method, the default implementation will be created automatically.

ControllerMethods is such a trait interface which provides all methods and properties that Grails adds to the controller. The beauty is that we use a static compiler and have both performance and compile time checks.

Voila. We have a fully statically typed controller. No black magic involved.

I hope it was interesting. You're welcome to learn more at the Groovy++ project page.

Thank you for reading and till next time.

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

Comments

Marko Milicevic replied on Tue, 2010/11/16 - 10:25am

How would you handle the case where a plugin injects some additional properties/methods into the controller at runtime, or if the user created some additional dynamic local objects?  Would that be disallowed by declaring the Controller @Typed?

Also, how would you handle testing scenarios where you might normally duck type "request" and "chatService" with mock objects?

The idea of moving some dynamic resolution to the compilation phase via AST transforms is interesting, especially as an alternative to traditional static code generation. 

I have not had enough time to meditate on it, but i am still working on understanding the true nature of dynamic vs static, particularly is there some way to unify them and so we can have-our-cake-and-eat-too?-)

 

Alex Tkachman replied on Tue, 2010/11/16 - 11:57am in response to: Marko Milicevic

TestedController ctr = [ getRequest: {... }]

Dmitry Petukhov replied on Sat, 2010/11/27 - 11:15am

I like this idea a lot. Looks like these approach will become usable as soon as someone implements dynamic methods of Domain classes defined in org\codehaus\groovy\grails\plugins\orm\hibernate\hibernatepluginsupport.groovy.  
see http://www.grails.org/DomainClass+Dynamic+Methods

Any plans ? ;)

Comment viewing options

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