Sunday, September 6, 2009

Groovy Goodness: Using the Inject Method

Groovy has some features and methods we can categorize as functional programming. The inject() method is a so called higher-order function. Other languages call it a fold, reduce or accumulate. The inject() method processes a data structure with a closure and builds up a return value. The first parameter of the inject() method is the first value of the intermediary results of the second parameter: the closure. When we use the inject() we don't introduce any side effects, because we build up the value without using any outside variable.

To understand the inject() method better we look at some sample code:

// Traditional "sum of the values in a list" sample.
// First with each() and side effect, because we have
// to declare a variable to hold the result:
def total = 0
(1..4).each { total += it }
assert 10  == total

// With the inject method we 'inject' the 
// first value of the result, and then for
// each item the result is increased and
// returned for the next iteration.
def sum = (1..4).inject(0) { result, i -> result + i }
assert 10 == sum

// We add a println statement to see what happens.
(1..4).inject(0) { result, i ->
    println "$result + $i = ${result + i}"
    result + i
// Output: 
// 0 + 1 = 1
// 1 + 2 = 3
// 3 + 3 = 6
// 6 + 4 = 10

class Person {
    String username
    String email
def persons = [
    new Person(username:'mrhaki', email: 'email@host.com'),
    new Person(username:'hubert', email: 'other@host.com')

// Convert list to a map where the key is the value of
// username property of Person and the value is the email
// property of Person. We inject an empty map as the starting
// point for the result.
def map = persons.inject([:]) { result, person ->
    result[person.username] = person.email
assert [mrhaki: 'email@host.com', hubert: 'other@host.com'] == map

Run this script on GroovyConsole.


Tom said...

Thanks for the post. I liked the last snippet. Injecting a hashmap can be really useful.

Steve said...

And here's a cool GPath tool to boot!

assert ['email@host.com', 'other@host.com'] == persons.email


John Flinchbaugh said...

I notice the sum example didn't mutate the result, and returned new value to be used in next iteration. The map example mutates the map instead of unioning and returning a brand new map ( result + [(person.username): person.email] ). I toyed with the idea of trying to use maps immutably with the union, but performance was slower. Do you have any additional advice on this? Is mutating still the only way?

Post a Comment