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

Higher-order functions with Groovy, part 2

02.05.2008
| 10581 views |
  • submit to reddit

In part one I gave an overview of closures and currying, two features in Groovy that implement higher-order functions. Higher-order functions can be used in combination with regular Java classes. Java methods can be converted to closures and closures can be converted to Java methods.

To convert a Java method to a closure, place the ampersand sign (&) in front of the method name:

def isNotBlank = org.apache.commons.lang.StringUtils.&isNotBlank
assert isNotBlank instanceof Closure
assert isNotBlank("   text    ")

The inverse, to convert a closure to a Java method is equally easy. There is one restriction: you can only convert closures to method on interfaces. As an example I used the Specification pattern from the Domain-Driven Design book:

interface StringSpecification {
    boolean isSpecifiedBy(String s)
}

Now I can convert a closure to the isSpecifiedBy() method:

StringSpecification notBlankSpec =
    org.apache.commons.lang.StringUtils.&isNotBlank as StringSpecification

assert notBlankSpec.isSpecifiedBy("   text    ")

The as keyword is commonly used in Groovy to convert objects from one type to another. You can use the as keyword on closures to turn them into interface implementations. Groovy uses the java.lang.reflect.Proxy class for this.

One word of caution though. You can turn a closure into any interface, also interfaces that define multiple methods. Groovy will not complain. Here's an example:

Map m = org.apache.commons.lang.StringUtils.&isNotBlank as Map

assert m."   text     " == true // this calls the closure

If however you would call the put() method on java.util.Map you would get an error:

m.test = new Object()

Results in:

No signature of method: org.apache.commons.lang.StringUtils.isNotBlank() is applicable for argument types: (java.lang.String, java.lang.Object) values: {"test", java.lang.Object@1c783c5}

You can assign a specific closure to each method in an interface. Here's an example:

interface Visitor {
    void onSuccess(Object o)
    void onFailure(Throwable t)
}

Visitor v = [
    onSuccess: { println it },
    onFailure: { it.printStackTrace() }
] as Visitor

Here the as keyword is called on a Map object, converting it to an instance of the Visitor interface. This approach works well as long as methods are not overloaded. You can only assign one closure per method name. One closure would thus be called for all overloaded methods.

Since I'm working with closures I can convert Java methods into closures and curry:

import org.apache.log4j.*

Logger logger = Logger.getLogger(this.getClass())

Visitor v = [
    onSuccess: logger.&info,
    onFailure: {
        Closure _delegate, Throwable t ->

        logger.warn(_delegate(t))
    }.curry(org.apache.commons.lang.exception.ExceptionUtils.&getRootCause)
] as Visitor

v.onSuccess("Yeah")
v.onFailure(new RuntimeException(new NullPointerException()))

The call to the curry() method on line 11 passes the getRootCause() method as closure to the declared closure as an argument. This results in a closure that accepts a Throwable argument which is compatible with the onFailure() method on the Visitor interface.

The calls to the onSuccess() and onFailure() methods produces this output:

INFO - Yeah!
WARN - java.lang.NullPointerException

In the next and final installment I'll discuss how calls to methods and properties are handled inside closures.

Update: part three 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

Robert O'connor replied on Thu, 2008/04/17 - 1:36am

amazing =)

Comment viewing options

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