Getting Groovy With "with" Thursday, November 13, 2008

UPDATE 12/15/2008: I committed a change yesterday that changes the behavior of the .with method to use DELEGATE_FIRST as the resolveStrategy instead of OWNER_FIRST. If you are not sure what that means, you should by the end of this post.

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.

Enjoy! :)

48 comments:

Ken Sipe said...

This is great information and funny as heck!

robpatrick said...

Jeff, great post thanks.
Jeff, really enjoyed it.

Ronald said...

Good stuff, Jeff. And Opeth Rocks btw :-) Gonna see them next week in Amsterdam.

Jeff Brown said...

robpatrick: Funny! Thanks.

ronald: Thanks. I hope you enjoy the show. I saw them most recently a few weeks ago here in St. Louis. Good stuff!

Matthew Taylor said...

NEWS FLASH: Jeff Brown shows Matt Taylor yet another Groovy language feature he can no longer live without.

Mario said...

I find this kind of programming very intuitive. Ruby 1.9 introduces a new method to the Object class called instance_exec which allows you to do this same kind of thing. I abhor typing so I think with is a better method name than instance_exec. *sigh*

Yakov said...

Mario:

Try this:

class Object
  def with(&b)
    self.instance_exec(&b)
  end
end

Jim Shingler said...

Darn it, . . . ;-) now I have to go back and simply some of my code.

Very Nice!


Thanks for the tip

Jim

Ammad Zafeer said...

with all the plugins there are on grails.org, is there one for admin to change the theme instead of storing it in a cookie with "Skinnable" plugin. i have been looking for something like that for sometime. maybe you can guide me in the right direction. thanks

p.s. this was really funny!

Marc Palmer said...

Nice one Jeff. I have never used with() in groovy to this day. I knew it was there but... thought there were some issues with scoping.

Anyway, all I can say is LONG LIVE PASCAL man. Groovy rocks, but Pascal is the daddy. Pascal had "with" back in the 80s!

...and Pascal sets and ranges are still superior to anything we have these days... except perhaps the limit to 32/64bit range/members on the sets and ranges eh :)

Right I'm off to write a dynamic version of Delphi. We could call it Dynamic Pascal. Or Dyphi. Or Daphne.

James E. Ervin, IV said...

That is a nice example, also gave me a better understanding of Closures. I admit I have been just using them without understanding much. I know I have dealt with this in Java by using return values, if the method (particularly a setter) returns void, have it return the object it refers to. Makes the code cleaner, but will not help with the other 99.99999% of Java code that has already been written. Sigh makes me wish to use Groovy in my development work more. Need to work on getting Eclipse PDE working better with Groovy so that I can.

James E. Ervin, IV said...

Also be nice to get some IDE support for this, thanks for the article you got my brain cells charged and running.

Katie said...

Nah, I still think the major problem is the lack of appreciation of Swedish metal. ;)

Fungible said...

Jeff, I thought your post was going to solve my dilemma, but it didn't help. I'm trying to figure out how to access "this" at evaluation time, not closure creation time.

For example, I create a dynamic getter and setter by adding them to the metaClass. But when the getter or setter is evaluated, I can't access fields of the object whose property is being accessed, because "this" was locked down when the closure was first created.

If this doesn't make sense, there's a thread named Combining evaluate with MOP on the groovy-user mailing list where I've posted broken code. :-)

I sure could use a solution to this. I'm confident I'm doing something wrong. I just don't know how to fix it.

BTW, I enjoyed your presentation last time you were at Atlanta JUG.

Ni said...

Thanks for this post.
I've just tried

someobject.with { myclosure() }

where 'myclosure' tried to call methods of 'someobject' and it didn't work.

But with
myclosure.delegate = someobject
myclosure()
it does. This is awesome.

Anonymous said...

酒店打工 酒店兼職
台北酒店 打工兼差 酒店工作
酒店經紀 禮服店
酒店兼差 酒店上班 酒店PT 酒店酒店喝酒 酒店消費 喝花酒 粉味

Purohit D said...

Sun Java is one of the most flexible platform for application development Sun Java development gives the way to develop complex applicaton development.

cc22 said...

角色扮演,
睡衣,
SM,
潤滑液,
情趣玩具,
愛愛,

情人趣味用品,
情人趣味千奈,
情人趣味愛戀,
情趣味用品,
情趣用具,

跳蛋,
G點,
按摩棒,
跳蛋,
飛機杯,
充氣娃娃,
自慰套,
情趣娃娃,
自慰器,
情趣用品,情趣,

viagra online said...

hahah the conversation kills me xD!
It's like a conversation between, php and oracle... who can understand that, because like the guy there's not point in saying a name at the begining of every sentence ? now there's any kind of logic of doing it ? what you think ?

Einar Jónsson said...

Great post, I'm definitely going to start using the with() method more in my Groovy code.

P.S. The new Opeth DVD (Royal Albert Hall) is excellent ;-)

Nirav Assar said...

Jeff, there is also an identity closure that people used for setters and so on for objects. What is the difference between with and identity. Thankss

Nirav Assar said...
This comment has been removed by the author.
seositeden.blogspot.com said...

Thanks for your article, quite useful piece of writing.

limo service in EAST Brunswick said...

Thanks for your post, I like this post very much.

Logo Design Company said...

This is one of the most incredible blogs I have read in a very long time. Your blog is great for anyone who wants to understand this subject more. Great stuff.
Brochure Design

Abby said...

WAO its totally indifferent i'm so impressed the way you presented this

Buy Acne creams said...

Great tips, many thanks for sharing. I have printed and will stick on the wall! I like this blog.

maquinas de coser said...

There are some very great sources here and thank you for being so kind to post them here.

maquinas de bordar

Natural supplements said...

I agree with you. This post is truly inspiring. I like your post and everything
you share with us is current and very informative, I want to bookmark the page
so I can return here from you that you have done a fantastic job ...

Natural supplements

Natural supplements said...

I had a great time reading your article and I found it interesting.
This is such a beautiful topic that my friends are talking about. Thanks
for this blog, we are enlightened.

Natural supplements

best over the counter sleep aid said...

This is one of the most incredible blogs I have read in a very long time. Your blog is great for anyone who wants to understand this subject more. Great stuff; please keep it up!

ignition lock said...

Thanks for your marvelous posting! I actually enjoyed reading it, you will be a great author.I will ensure that I bookmark your blog and will come back in the foreseeable future. I want to encourage that you continue your great job, have a nice weekend!think you’ve made some truly interesting points

organic supplements said...

nice to see it thanks for sharing it

John Anderson said...

Great stuff Buy Aldara Cream . The information and the detail were just perfect. Order Biltricide 600mg think that your perspective is deep, its just well thought out and really fantastic to see someone who knows how to put these thoughts down so well. Great job on this.

Locksmith Washington DC said...

I am very happy to be here because this is a very good site that provides lots of information about the topics covered in depth.

city locksmith said...

Great information & Funny,I like this Blog,thanks for sharing.

floridagator forum said...

Absolutely fantastic topic! Great blog. Thanks for taking the time and writing this

[url=http://www.gatorsportsnation.com]floridagator forum[/www.gatorsportsnation.com]

laptop repair melbourne said...

I am speechless after seeing these pictures! I love them all! I teach kindergarten and I'm going to make a theme, and photographs have given me so many ideas! You are so talented! Thanks!

laptop screen fix melbourne said...

You will be missed.The one place where as a venue and fan I could search by what ever perimeters I chose .Good Luck with your future endeavors.

melbourne pc repair said...

I am speechless after seeing these pictures! I love them all! I teach kindergarten and I'm going to make a theme, and

photographs have given me so many ideas! You are so talented! Thanks!

Paxil SSRI said...

Your web site is nice really, I actually have seen your post which was terribly informative and extremely entertaining on behalf of me. Thanks for posting extremely Such Things. I ought to suggest your web site to my friends. Cheers.

malini ecorp said...

) Ecorptrainings.com provides GROOVY in hyderabad with best faculties on real time projects. We give the best online trainingamong the GROOVY in Hyderabad. Classroom Training in Hyderabad India

Anonymous said...

I agree with this article completely, I must thank you for posting such helpful facts.writing essay

Anonymous said...

Always so interesting to visit your site.What a great info, thank you for sharing. this will help me so much in my learning.custom logo design service

Anonymous said...

This blog is very useful information for me and guidance.support in dissertation , thesis provider , writing service , pay for accounting help , uk university , written papers

niki194 said...

What could be better than having some extra cash when traveling? A payday loan site comes in handy for instant no credit approval and for a loan of upto $1500.

http://www.paydaycashloansnow.com/

niki194 said...

What could be better than having some extra cash when traveling? A payday loan site comes in handy for instant no credit approval and for a loan of upto $1500.
PAYDAY LOAN

Fitz said...

@Marc Palmer: Thank you! I thought I was going crazy, because I was certain that there was a with token in Java, but I must have been remembering my Pascal/Delphi days! I remember spending $55 at the University bookstore for a copy of Turbo Pascal and developing console applications. I didn't have the $80 for Turbo Pascal for Windows, which came out within a few weeks of me buying my copy of Turbo Pascal. LOL