Sunday, December 7, 2014

Moving to idiomatic Groovy - 1

Lets get started with an example
import groovy.transform.ToString

@ToString(includeNames=true)
class Geek{
    String name
    int age
    List<String> languages
}
def geeks = []
geeks << new Geek(name: 'Raj', age: 24, languages: ['Java', 'Groovy'])
geeks << new Geek(name: 'Arun', age: 35, languages: ['Java', 'Scala', 'Clojure'])
geeks << new Geek(name: 'Kumar', age: 28, languages: ['Groovy', 'Scala'])
Q - Find out all geeks who know Groovy.
Lets do it in imperative way first.
def groovyGeeks = []
for(geek in geeks){
    if(geek.languages.contains('Groovy')){
        groovyGeeks << geek
    }
}
println groovyGeeks
And we get the following result
[Geek(name:Raj, age:24, languages:[Java, Groovy]), Geek(name:Kumar, age:28, languages:[Groovy, Scala])]
Does this look great? - Not really.
Lets make it more idiomatic groovy
def groovyGeeks = geeks.findAll({it.languages.contains('Groovy')})
println groovyGeeks
Here I have used findAll method, which accepts a closure - a predicate, which decides if an item in the collection to be part of the output. it is the default parameter.
Groovy provdes a syntactic sugure - if the last argument to a method is a closure, then it can be specified outside the parenteses.
def groovyGeeks = geeks.findAll{
    it.languages.contains('Groovy')
}
println groovyGeeks
Lets refactor the code slightly, and extract the closure into a variable.
def knowsGroovy = { geek -> geek.languages.contains('Groovy')}
def groovyGeeks = geeks.findAll{
    knowsGroovy(it)
}
println groovyGeeks
If you have programmed in any functional language, you could recognize findAll as a higher order function.
Well thats ok, but what if I want find out all the ‘Clojure’ developers?
Lets generalize knowsGroovy to work for any language.
def knowsLanguage = {geek, language ->
 geek.languages.contains(language)}

def findAllExperts = { devs, language ->
    devs.findAll { knowsLanguage(it, language) }
}
println findAllExperts(geeks, 'Groovy')
Now suppose you want to invoke findAllExperts on several collections with the second argument fixed as ‘Groovy’, you could create a curried closure as follows
def findAllGroovyExperts = findAllExperts.rcurry('Groovy')
println findAllGroovyExperts(geeks)