Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Higher-order functions with Groovy, part 3

DZone's Guide to

Higher-order functions with Groovy, part 3

· Java Zone
Free Resource

Managing a MongoDB deployment? Take a load off and live migrate to MongoDB Atlas, the official automated service, with little to no downtime.

In the first and second parts I've talked about how closures work in Groovy, how they can be curried, how Java methods can be converted to closures and how closures can be converted to Java methods.

In this last part I'll talk about two other aspects of closures: their delegating mechanism and the Groovy builder syntax.

Consider this script:

def log(String message) {
    println message
}

def cl = {
    log("Hello, World!")
}

cl()

Which log() method gets called on line 6? Intuitively, it must be the log() method declared on lines 1-3. And for this script that's the correct answer. But that doesn't have to be the case.

Calls to methods and properties from within closures are delegated. By default - as in the example above - calls are delegated to the owner of the closure. The owner is read-only property that gets assigned when the closure is created.

Let's print out all methods on the class of the owner object:

def log(String message) {
    println message
}

def cl = {
    owner.getClass().declaredMethods.each {
        log("${it}")
    }
}

cl()

The log() method is third in the list (delegate_test.groovy is the name of my script file):

public static transient void delegate_test.main(java.lang.String[])
public java.lang.Object delegate_test.run()
public java.lang.Object delegate_test.log(java.lang.String)
[...]

But calls do not have to be delegated to the owner object. They can also be delegated to the delegate object. The delegate is a read-write property that by default holds the same object as the owner property.

Consider this script:

def log(String message) {
    println message
}

def cl = {
    log("Hello, World!")
}

assert cl.delegate == cl.owner

cl.delegate = null
cl.resolveStrategy = Closure.DELEGATE_ONLY

cl()

On line 6 the closure calls the log() method. On line 11 the delegate property of the closure (which is declared in the groovy.lang.Closure class) is set to null. On line 12 the resolveStrategy property is set to DELEGATE_ONLY. When I run this script I get an error:

Caught: groovy.lang.MissingMethodException: No signature of method: delegate_test$_run_closure1.log() 
            is applicable for argument types: (java.lang.String) values: {"Hello, World!"}
at delegate_test$_run_closure1.doCall(delegate_test.groovy:6)
at delegate_test$_run_closure1.doCall(delegate_test.groovy)
at delegate_test.run(delegate_test.groovy:14)
at delegate_test.main(delegate_test.groovy)

Since there is no delegate object set on the closure, and the resolve strategy for delegating calls is set to DELEGATE_ONLY the delegation of the call fails with a MissingMethodException.

But why would you want to set a delegate object on a closure? Builders in Groovy do this in order to control which methods get executed from within closures. This is an example of the builder syntax for passing closures to methods:

html {
    head {

    }
    body {

    }
}

The three methods that are called - html(), head() and body() - will get a Closure object as last argument value. Here's an example of how the html() method could be declared:

def html(Closure cl) {

}

When methods receive a Closure object they need to call it, otherwise it won't get executed. For example, the empty method body of the html() method above does not call the closure it receives so the the head() and body() methods will never be called.

To remedy this the closure object has to be called. This could be as simple as this:

def html(Closure cl) {
    cl()
}

However, for this to work the head() and body() methods need to be declared on the class of the owner object:

def html(Closure cl) {
    cl()
}

def head() {

}

def body() {

}

This may not be what you want since now users can call the head() or body() method without calling the html() method first. The solution is to move the head() and body() methods to another class, and set a delegate object to the closure:

def html(Closure cl) {
    cl.delegate = new HtmlDelegate()
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
}

class HtmlDelegate {
    def head() {

    }

    def body() {

    }
}

By setting the delegate object on line 2 and the resolveStrategy property to DELEGATE_FIRST methods, properties and variable can still be called on the owner object. The delegate object however gets priority.

When setting the delegate and resolveStrategy properties you change the state of the Closure object and this may cause thread-safety problems. This is typically not the case for Groovy builders, but it may be the case in other scenarios.

To remedy this problem, call the clone() method on a Closure object:

def html(Closure cl) {
    def copy = cl.clone()
    copy.delegate = new HtmlDelegate()
    copy.resolveStrategy = Closure.DELEGATE_FIRST
    copy()
}

That's it for higher-order functions with Groovy. There are a few more things to tell but with what you've learned in this series you should be able to get a long way.

Higher-order functions offer an alternative paradigm to the classic Java methods we are so used to. It's a different way of constructing programs, and often it's more efficient. The builder syntax is just one example of higher-order functions at work. You can apply any of these techniques yourself.

Converting Java methods to higher-order functions and back is what makes their implementation in Groovy so interesting for me. If nothing else, it gives me an alternative way of playing with existing Java code that's not available without Groovy.

Happy coding!

MongoDB Atlas is the easiest way to run the fastest-growing database for modern applications — no installation, setup, or configuration required. Easily live migrate an existing workload or start with 512MB of storage for free.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}