Higher-order functions with Groovy, part 1

I'll admit, higher-order functions sounds like link bait for over-achievers. Trust me, I didn't invent the term :-) A Higher-order function is a concept from mathematics where a function accepts other functions as its arguments, and can return functions as results.

Higher-order functions are related to functional programming but higher-order functions != functional programming. In computer science higher-order functions consists of two things: closures and currying. Groovy supports both :-)

So what's a closure then? Closures are not unique to Groovy. Ruby, Lisp, JavaScript and D have closures as do many other languages.

A closure in Groovy is three things:

  1. a block of code that can access variables in the scope where it is declared.
  2. a function that can take arguments and always returns a result (may be null)
  3. an object that has properties and methods with and without side-effects

Calling a closure if thread-safe if the implementation is thread-safe. Here's an example of a closure:

def x = { println it }

And here's how you call it (two options):

x('Hello, world!')
x.call('Hello, world')

Closures can take arguments, including other closures:

def isList = { i -> i instanceof List }
if (isList([])) {
    println "This is a List"
}

Closure arguments can be typed:

def prefix = { 
    String s -> 
    while (s.length() < 17) {
        s = "0$s"
    }
    s // return keyword is not required
}
def id = prefix "1234" // parentheses are not required

Closures can be passed as arguments, for example to the each() method on java.util.Map:

System.properties.each { println it }

And a closure can call itself recursively:

// Thanks to Sergey Bondarenko for this one-liner
def fac = { int i -> i == 1 ? 1 : i * call(i - 1) }
println fac(10) // parentheses are required for fac since I call println without

Closures can access the variables in the scope where they are declared:

def pi = 22 / 3
def calcSurface = { radius -> pi * (radius * radius) }
def surface = calcSurface 10

Currying is closely related to closures. With curring you can construct programs by appending argument values to closures:

def appendForLength = {
    int length, String charachter, String toBeAppended ->

    while (toBeAppended.length() < length) {
        toBeAppended = "${character}${toBeAppended}"
    }
    return toBeAppended
}
def myKindOfId = appendForLength.curry 17, "0"
assert "00000000000012345" == myKindOfId("12345")

The call to the curry() method on line 9 passes two arguments to the appendForLength closure and returns a new closure. This new closure takes one argument which is actually the third argument of the appendForLength closure.

And with currying you can go beyond Groovy closures, you can also curry any Java method. First you need to know how to turn a method into a closure. You add the ampersand (&) character in front of the method name:

def getProperty = System.&getProperty

The value that is returned is a closure:

def getProperty = System.&getProperty
getProperty("java.version")

And since it's a closure you can curry it:

def getProperty = System.&getProperty
def javaVersion = getProperty.curry("java.version")
assert "1.5.0_04" == javaVersion()

Higher-order functions simplify programs. It's closely related to functional programming. You've learned how to convert regular Java methods to higher-order functions. You can also do the inverse: convert higher-order functions to Java interface methods. This and other techniques will be the subject of the second installment.

Update: part two has been posted too. Happy coding!
3.833335
Average: 3.8 (6 votes)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

vaclav replied on Sun, 2008/02/03 - 12:35pm

Thanks for the article. It is very consise yet rich on useful info. For example, this is the first time I see how a closure can call itself recursively. Even the excellent GINA book doesn't tell how to do that, or at least I couldn't find it.

Dierk Koenig replied on Sun, 2008/02/03 - 6:43pm in response to: vaclav

You are right. GINA doesn't cover this feature since it was not yet officially supported at the time of writing.

keep groovin'

Dierk

Craig replied on Sun, 2008/02/03 - 10:30pm

I seem to get an exception when running the 4th code block:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, Script7: 9: expecting EOF, found '1234' @ line 9, column 17.
1 error

 It seems if I don't use parenthesis I get the exception:

BAD

def id = prefix "1234"

GOOD

def id = prefix("1234")

 

 I am using Groovy 1.5.0 if that matters.

 

http://www.codecraig.com - Stuff. Online.

vaclav replied on Mon, 2008/02/04 - 12:42am in response to: mitite

Oh, that explains it. Thank you, Dierk.

Tiago Antao replied on Thu, 2008/02/14 - 5:53am

Great summary/tutorial article, thanks

Robert O'Connor replied on Wed, 2008/04/16 - 2:42am

Greata article

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.