Groovy Notes
From SubfireWiki
I've been playing with Groovy lately. Here are some notes/questions.
Where's the doc?
- JSR Spec
- User Guide
- Getting Started Guide
- Getting to know Groovy
- Dev works articles
- extra methods for standard Java classes. Note the additions to java.io.File and java.util.Collection
Convenience with file manipulation
There are a bunch of convenience methods that are added to the java.io.File class: readLines(), eachFile() {for directories}, eachLine(). For example, readLines():
n=1; new java.io.File("/etc/hosts").readLines().collect({"${n++}: ${it}"}).each({println it})
That reads from /etc/hosts into a list, prepends a line number and then prints out the list, with newlines between lines.
How does <collection>*.<operation>() work?
I was hoping * would work like map(), but it doesn't quite seem to. You can only use operations that exist on the items in the list, rather than apply any given closure to the items.
> println ( ["a", "bb"]*.size() ) [1, 2]
Which is convenient I suppose, but it would be much more useful to be able to put any closure in there
It turns out that Collections.collect() is the thing:
assert [2,4,6] == [1,2,3].collect({it*2})
Functions can't be curried (but closure can)
Closures can be curried (just use <closure>.curry(<args>)), which is great... if you've got a closure. It would be convenient to be able to curry methods. This is probably a limitation of the JVM, but it seems sensible to have named functions work similarly to closures.
Maps are cool, but with slightly different syntax from python
def emptyMap = [:]
emptyMap.foo = "bar"
emptyMap["baz"] = "quux"
def anotherMap = [foo: "bar", ("baz"): "quux"]
assert emptyMap == anotherMap
anotherMap.each() {println it}
anotherMap.each() {k, v -> println "${k}=${v}"}
Note that both each() calls will print the same thing, but for rather different reasons (it is a name/value pair -- a HashMap.Entry whose toString() does that)
So there's a significant syntactic pressure to make map keys be strings (though other objects are certainly possible)
Builders are cool -- making domain specific languages possible
I gotta look at these builders more, but MarkupBuilder and XmlSlurper seem pretty slick. There are builders for Swing and SWT as well. Also SQL.
Expandos are cool -- allowing object prototyping
The name is a littly silly (like Groovy's, I suppose), but Expandos allow object prototyping. The doc says they're like maps or dictionaries with closures with an implicit this for lookups. Groovy's map syntax makes this a bit more natural and sort of reifies something that was possible before (by simply putting a callable in map is possible, but you'd have to pass in the map manually).
Expressions in GStrings
Very similar to quasistrings in JScheme (or maybe perl?), though I don't know how many levels this can go, maybe just one. You can put expressions in strings and they'll be evaluated at runtime.
def name = "Result:"
println "${name} 6+5 = ${6+5}"
This makes SQL generation pretty nice (for example). I haven't yet decided how much better this is to python's variable-in-string expansions.
List comprehensions -- nope can't do it
Apparently no list comprehensions other than the (very simple) 1..10 syntax.
Lazy evaluation -- not built in, but can be simulated
This actually ended up being a little hairy to code up because so much has to be simulated with closures.
class UnboundedList {
def head
private Closure tail
UnboundedList(def head, Closure tail) {
this.head = head
this.tail = tail
}
def UnboundedList getTail() { tail?.call() }
def realize(sz) {
if(sz == 0) return []
def t = getTail()
if(t ==null) return [head]
def realized = t.realize(sz-1)
realized?.add(0, head)
realized
}
def map(f) { new UnboundedList(f(head), {tail?.map(f)}) }
def filter(f) { f(head) ? new UnboundedList(head, {tail?.filter(f)}) : getTail()?.filter(f) }
def foldl(f, i) {
def v = f(i, head)
def t = getTail()
t == null ? v :
t.foldl(f, v)
}
}
def successors(n) { new UnboundedList(n, {successors(n+1)} ) }
def positiveIntegers = successors(1)
println positiveIntegers.realize(5)
println positiveIntegers.map({x -> x*2}).realize(5)
println positiveIntegers.filter({x -> x %3 == 0}).realize(5)
def threeNumbers = new UnboundedList(1, {new UnboundedList(2, { new UnboundedList(3, null)} )} )
println threeNumbers.realize(2)
println threeNumbers.realize(4)
println threeNumbers.foldl({a, b -> a+b}, 0)
println threeNumbers.foldl({a, b -> a*b}, 2)
Result:
[1, 2, 3, 4, 5] [2, 4, 6, 8, 10] [3, 6, 9, 12, 15] [1, 2] [1, 2, 3] 6 12
No overloading/pattern matching/parametric polymorphism
There's just one value for each name (unless I'm mistaken, apparently, you have to buy the book to get the full description of the language).
