Steven has posted 36 posts at DZone. View Full User Profile

Higher-order functions with Groovy, part 1

02.03.2008
| 23452 views |
  • submit to reddit

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!
Published at DZone with permission of its author, Steven Devijver.

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

Comments

Václav Pech 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: Václav Pech

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 Wickesser 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.

Václav Pech replied on Mon, 2008/02/04 - 12:42am in response to: Dierk Koenig

Oh, that explains it. Thank you, Dierk.

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

Great summary/tutorial article, thanks

Hsufeng Ko replied on Tue, 2010/02/23 - 8:10pm in response to: Craig Wickesser

Same error. I'm on 1.7.1 groovyConsole.

Comment viewing options

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