Jeff has posted 1 posts at DZone. View Full User Profile

Getting Groovy With "with"

11.14.2008
| 21040 views |
  • submit to reddit

Strange enough title.Let's start with a hypothetical conversation between a geeky developer and his much less geeky wife:


Jeff: Betsy, how are you?

Betsy: I am fine thanks. How are you?

Jeff: Betsy, I am fine thank you.

Betsy: Great.

Jeff: Betsy, you know my birthday is the day after tomorrow right?

Betsy: Yes, I haven't forgotten. You mention it about 9 times a day you know.

Jeff: Betsy, yes I know. Are we going to have an ice cream cake?

Betsy: Yes, I think that would be good.

Jeff: Betsy, are you going to buy me the new Opeth DVD?

Betsy: I will get it for you but that music sucks big time.

Jeff: Betsy, that is awesome. Thank you.

Betsy: Why do you keep saying "Betsy" at the beginning of every sentence?

Jeff: Betsy, I guess I am used to inflexible languages which aren't very expressive.



Um, what does any of that have to do with Groovy? Well, lets talk about the problem with this conversation (aside from the lady's lack of appreciation for Swedish heavy metal). What is wrong is Jeff begins each sentence with "Betsy". Why might he do that? One reason is so Betsy knows that he is talking to her. Clearly this isn't necessary. It isn't necessary because she already knows he is talking to her. A context has been established which makes the addressing unnecessary. Jeff began the conversation by addressing Betsy, they probably made eye contact and were in close proximity. Once the conversation started, there isn't much question about who each sentence is being directed to.

Again, what does any of that have to do with Groovy? Lets press on...

Consider the following Java code which prints out a specific date.

// PrintIndependenceDay.java

import java.util.Calendar;
import java.util.Date;

public class PrintIndependenceDay {

public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(Calendar.MONTH, Calendar.JULY);
calendar.set(Calendar.DATE, 4);
calendar.set(Calendar.YEAR, 1776);
Date time = calendar.getTime();
System.out.println(time);
}
}



Groovy developers can look at that and find quite a bit of noise that doesn't really have anything to do with what the code is trying to do but I want to focus on one specific thing. That one thing is all of the interaction with the calendar variable. Notice that we call clear() on the calendar, then call set() several times and later call getTime() on that same variable. All of those calls are prefixed with "calendar." so the compiler knows what we want to do. If we called "clear()" instead of "calendar.clear()", what would that mean? Are we calling the clear() method in this class? If that was our intent, it would not work because there is no clear() method. We have to prefix the call with an object reference so the compiler knows where to send the request. That seems to make sense. However, if we are going to do a bunch of things "with" the same object, wouldn't it be kind of nice if we could somehow do all of those things without all the repetition. Specifically, it might be nice if we could get rid of all of those "calendar." prefixes.

On to Groovy...

The following Groovy code does the same thing that the Java code above does.


// PrintIndependenceDay.groovy

def calendar = Calendar.instance
calendar.with {
clear()
set MONTH, JULY
set DATE, 4
set YEAR, 1776
println time
}



Wow. That is a good bit cleaner than what we started with. Part of the reason for that is we were able to get rid of all of those "calendar." prefixes. What allowed us to do that is calling the "with" method on the calendar object and passing a closure as an argument. What we have done there is establish a context that says "do all of this stuff with this calendar object". When the closure executes, the calendar is given an opportunity to respond to method calls like clear() and set() and the implicit call to getTime() when referring to the "time" property which is being passed to println. Likewise, the references to MONTH, JULY, DATE and YEAR properties are also being handled by the calendar object.

That is pretty slick. Lets dig just a little deeper to get a little better understanding of what is really going on.

Groovy closures have a delegate associated with them. The delegate is given an opportunity to respond to method calls which happen inside of the closure. Here is a simple example:


// define a closure
def myClosure = {
// call a method that does not exist
append 'Jeff'
append ' was here.'
}

// assign a delegate to the closure
def sb = new StringBuffer()
myClosure.delegate = sb

// execute the closure
myClosure()

assert 'Jeff was here.' == sb.toString()



When the closure is executed, those calls to append() in the closure end up being sent to the delegate, the StringBuffer in this case.

Something similar is happening in the Groovy calendar code above. A closure is being passed to the with() method. That closure is calling methods like set() and getTime() which don't really exist in that context. The reason those calls don't fail is the with() method is assigning a delegate to the closure before it is executed. The delegate being assigned is the object that the with() method was invoked on. In the calendar example, the delegate is the calendar object. Something like this is happening...


def closure = {
clear()
set MONTH, JULY
set DATE, 4
set YEAR, 1776
println time
}
def calendar = Calendar.instance
closure.delegate = calendar
closure()



This code does the same thing as the first Groovy example. Obviously the first one is cleaner.

I sort of lied a bit, or at least left out a detail that may be significant. The closure that is being passed to the with() method is really being cloned and it is the clone that is assigned the delegate and executed. This is a safer approach than monkeying with the original closure. If the reasons for that aren't clear, the explanation is another story.

Another bit of info that is missing here is the strategy that a closure uses to decide when to send method calls to the delegate. Each Groovy closure has a resolveStrategy associated with it. This property determines how/if the delegate comes in to play. The 4 possible values for the resolveStrategy are OWNER_FIRST, DELEGATE_FIRST, OWNER_ONLY and DELEGATE_ONLY (all constants defined in groovy.lang.Closure). The default is OWNER_FIRST. Consider the owner to be the "this" wherever the closure is defined. Here is a simple example...


class ResolutionTest {

def append(arg) {
println "you called the append method and passed ${arg}"
}

def doIt() {
def closure = {
append 'Jeff was here.'
}

def buffer = new StringBuffer()

closure.delegate = buffer

// the append method in this ResolutionTest
// will be called because resolveStrategy is
// OWNER_FIRST (the default)
closure()

// give the delegate first crack at method
// calls made inside the closure
closure.resolveStrategy = Closure.DELEGATE_FIRST

// the append method on buffer will
// be called because the delegate gets
// first crack at the call to append()
closure()
}

static void main(String[] a) {
new ResolutionTest().doIt()
}
}



So you see how the with() method helps establish a context where a bunch of things may happen "with" a specific object without having to refer to that object over and over. This can help clean up code like the Java code we started with. This approach can also be really useful when building a domain specific language in Groovy, a topic for another time.

From http://javajeff.blogspot.com/

References
Published at DZone with permission of its author, Jeff Brown. (source)

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

Tags:

Comments

Jacek Furmankiewicz replied on Fri, 2008/11/14 - 9:58am

You know, Visual Basic has had "with" since version 3 or so....so it's not really a Groovy invention :-)

Probably the only part of that language I ever thought was cool. *grin*

Aswani Kumar replied on Fri, 2008/11/14 - 2:36pm

Pretty good article. The type I wish were there more often.

Jeff Brown replied on Fri, 2008/11/14 - 3:15pm in response to: Aswani Kumar

Thanks Aswani.  I am glad you enjoyed it.

Ingo Richter replied on Mon, 2008/11/17 - 4:12am

Thanks for this example. I was looking for something like this. It appeared at the right time on my radar.

Wilhelm Fitzpatrick replied on Tue, 2008/11/18 - 1:40am

I like alot of things about Groovy, and "with" seems like a nice thing to have, but I'm bothered by the fact that it currently seems have a toy implementation.  Consider:

 class Foo {
    def add(x) {
        println "wrong add: $x"
    }

    def doit(x) {
        def n = [1,2,3]
       
        n.with {
          add(x)
          each { println it }
        }
       
        println n
    }

    def test() {
        doit("before")
    }

}

new Foo().test()

wrong add: before
Foo$_doit_closure1@f3724c
[1, 2, 3]

(tried w/Groovy 1.5.6 and 1.6.0-beta2)

The first call in the with closure (add) is goes not to the list, but to the add in the owner scope. This shows me that using with leaves me open to inadvertent breakage in the future if I happen to add a method with the "wrong" name to a class that uses a with clause in a completely different method.  This I believe could be addressed if the with implementation changed the dispatch order of its closure to DELEGATE_ONLY.

The problem with the each{} goes deeper, as it is being sent to the "invisible this" of the closure object itself, which, since all collection methods exist on Object is present, although unexpected.  Which means "with" cannot be used to call any method defined on object, which in Groovy is alot.

With is a cool feature, but Groovy's with does NOT seem ready for prime time...

James Ervin replied on Wed, 2008/12/03 - 10:52am

Jacek, that is one aspect of Groovy I admire.  If they see a good feature in another language, they steal it.  Remember average artists copy, great artists steal!  I think that should be Groovy's motto for Java developers, "why let all the other non-Java developers have all the fun?"

 

John Rellis replied on Thu, 2010/03/11 - 8:18am

one of those posts that just made about 1000 things click into place in my head... well maybe not 1000... 998... maybe

Wish i had read this sooner :)  seems IDEA doesnt understand this too well...  JIRA!

Thanks!

Comment viewing options

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