Thursday, April 8, 2010

Groovy 1.7.2 - Three Features Worth the Upgrade

Groovy 1.7.2 was released 22 hours ago, so by now you have surely spent hours playing with the new bits and combing through the release notes looking for new nuggets of productivity.

Or maybe not.

For all those busier than me, here are the top 3 reasons to upgrade to the point release:

Convert GPathResults to XML - JIRA 1178

XmlSlurper is a great way to parse, manipulate, and generally tease XML. The problem is that once you start drilling into XML using the dynamic syntax, then you are working with GPathResults objects and not plain text or Strings. It was quite hard to pull out snippets of XML as text. No more! Now you can use XmlSlurper to pull out pieces of XML and then easily manipulate them as text by converting a GPathResult into a String.

Here is the classic XML structure from the XmlSlurper documentation:

def CAR_RECORDS = '''
<records>
<car name='HSV Maloo' make='Holden' year='2006' >
<country>Australia</country>
<record type='speed'>Production Pickup Truck with speed of 271kph</record>
</car>
<car name='P50' make='Peel' year='1962'>
<country>Isle of Man</country>
<record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg in weight</record>
</car>
<car name='Royale' make='Bugatti' year='1931'>
<country>France</country>
<record type='price'>Most Valuable Car at $15 million</record>
</car>
</records>
'''
Hopefully Blogger hasn't totally destroyed the XML tags. Yet one more reason to hate on XML. So anyway, XmlSlurper makes it trivial to pull out the second car element from the example XML:
def records = new XmlSlurper().parseText(CAR_RECORDS)
def secondCar = records.car[1]
assert secondCar.getClass() == NodeChild
assert secondCar.toString() == 'Isle of ManSmallest Street-Legal Car at 99cm wide and 59 kg in weight'
There are a few problems with this example, and it isn't that the second car element has an index of 1. The secondCar variable is of type NodeChild. This is great if you want to perform more dynamic matching against NodeChild, but not so great if you want to get the full XML of the child and all descendants. The toString() is pretty meaningless, it's just a concatenation of all the child node values. In 1.7.2, StreamingMarkupBuilder lets you quickly get the child XML as a String. The assertion and code sample is messy, but the feature isn't:
def xmlString = new StreamingMarkupBuilder().bindNode(secondCar).toString()
assert xmlString == "<car name='P50' year='1962' make='Peel'><country>Isle of Man</country><record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg in weight</record></car>"
Nice. I understand you could somehow get the XML in previous Groovy versions, but I never figured it out. I'm lucky if I can just get something to pretty print.

Easier Map and Property Sorting - JIRA 2597

It is easier than ever to sort Maps and Properties objects. The first step is to create an unsorted Map:
def map = [c: 1, b: 2, a: 3]
In previous Groovy releases there were two options to sort a Map. One sort method took a closure that would be invoked like a Comparator, and the other way is just to wrap the Map in a TreeMap. Here are some assertions showing how they worked:
assert map.sort { Entry it -> it.key }*.key == ['a', 'b', 'c']
assert new TreeMap(map)*.key == ['a', 'b', 'c']
Not bad, but now the Map object has a no-arg sort that does default sorting and an API that lets you pass a real Comparator:
assert map.sort()*.key == ['a', 'b', 'c']
assert map.sort({ a, b -> a <=> b } as Comparator)*.key == ['a', 'b', 'c']
And remember, these methods return a new Map, they do not mutate the original. No Maps are harmed during the sorting of Maps.

ncurry and rcurry - JIRA 4144

The curry method has been around Groovy for a long time. It lets you bind variables into closures from left to right:
def multiply = { a, b -> a * b }
def doubler = multiply.curry(2)
assert doubler(4) == 8
In the above example, multiply has a type signature of "Object -> Object -> Object" (two Object parameters and an Object return type). When you curry multiply into doubler you get a type signature of "Object -> Object" (one Object parameter and an Object return type). Parameters are bound from left to right. Curry always bound the left most parameter in the parameter list. Until now.

rcurry will bind parameters from right to left. So a halver can now be made from a divider. Whee!
def divider = { a, b -> a / b }
def halver = divider.rcurry(2)
assert 5 == halver(10)
ncurry is the API sibling of curry and rcurry. It lets you bind parameters based on the integer index of the parameter in the signature. So you can bind parameter 2, 3, 4, or whatever. Check it out:
def operation = { int x, Closure f, int y -> f(x, y) }
def divider = operation.ncurry(1) { a, b -> a / b }
assert 5 == divider(10, 2)
Before you go too crazy with ncurry and rcurry, be warned that I think there is a bug when you try to mix them. Using ncurry and rcurry alone seems to work great, but rcurrying an ncurried closure does not currently work. I have a hunch that this does not effect a whole lot of people. I'll post a comment when I know what is wrong or when it is fixed.

Now go upgrade to 1.7.2!

No comments: