Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 14 Next »

A closure in Groovy is an anonymous chunk of code that may take arguments, return a value, and reference and use variables declared in its surrounding scope. In many ways it resembles anonymous inner classes in Java, and closures are often used in Groovy in the same way that Java developers use anonymous inner classes. However, Groovy closures are much more powerful than anonymous inner classes, and far more convenient to specify and use.

Syntax for Defining a Closure

A closure definition follows this syntax:

Where closureArguments is an optional comma-delimited list of arguments, and statements are 0 or more Groovy statements. The arguments look similar to a method's parameter list, and these arguments may be typed or untyped. When a parameter list is specified, the | character is required and serves to seperate the arguments from the closure body. The statements portion consists of 0, 1, or many Groovy statements.

Some examples of valid closure definitions:

++++ Note: The examples could definitely be made more real-life MWS

Closure semantics

Closures appear to be a convenient mechanism for defining something like an inner classs, but the semantics are in fact more powerful and subtle than what an inner class offers. In particular, the properties of closures can be summarized in this manner:

  1. They have one implicit method (which is never specified in a closure definition) called doCall()
  2. A closure may be invoked via the call() method, or with a special syntax of an unnamed () invocation. Either invocation will be translated by Groovy into a call to the Closure's doCall() method.
  3. Closures may have 1...N arguments, which may be statically typed or untyped. The first parameter is available via an implicit untyped argument named it if no explicit arguments are named. If the caller does not specify any arguments, the first parameter (and, by extension, it) will be null.
  4. The developer does not have to use it for the first parameter. If they wish to use a different name, they may specify it in the parameter list.
  5. Closures always return a value. This may occur via either an explicit return statement, or as the value of the last statement in the closure body (e.g. an explicit return statement is optional).
  6. A closure may reference any variables defined within its enclosing lexical scope. Any such variable is said to be bound dynamically to the closure
  7. Any variables bound to a closure are available to the closure even when the closure is returned outside of the enclosing scope.
  8. Closures are first class objects in Groovy, and are always derived from the class Closure. Code which uses closures may reference them via untyped variables or variables typed as Closure.
  9. The body of a closure is not executed until it is explicitly invoked e.g. a closure is not invoked at its definition time
  10. A closure may be curried so that one a copy the closure is made with one or more of its parameters fixed to a constant value

These properties are explained further in the following sections.

Closures are anonymous

Closures in Groovy are always represented as anonymous blocks. Unlike a Java or Groovy class, you cannot have a named closure. You may however reference closures using untyped variables or variables of type Closure, and pass such references as method arguments and arguments to other closures.

Implicit method.

Closures are considered to have one implicitly defined method, which corresponds to the closure's arguments and body. You cannot override or redefine this method. This method is always invoked by the call() method on the closure, or via the special unnamed () syntax. The implicit method name is doCall().

Closure Arguments

A closure always has at least one argument, which will be available within the body of the closure via the implicit parameter it if no explicit parameters are defined. The developer never has to declare the it variable - like the this parameter within objects, it is implicitly available.

If a closure is invoked with zero arguments, then it will be null.

Explicit closure arguments may be specified by the developer as defined in the syntax section. These arguments are a list of 1 or more argument names which are comma seperated. The parameter list is terminated with a | character. Each of these arguments may be specified "naked" e.g. without a type, or with an explicit static type. If an explicit parameter list is specified, then the it variable is not available.

For arguments that have a declared type, this type will be checked at runtime. If a closure invocation has 1 or more arguments which do not match the declared argument type(s), then an exception will be thrown at runtime. Note that this argument type checking always occurs at runtime; there is no static type checking involved, so the compiler will not warn you about mis-matched types.

Groovy has special support for excess arguments. A closure may be declared with its last argument of type Object[]. If the developer does this, any excess arguments at invocation time are placed in this array. This can be used as a form of support for variable numbers of arguments. For example:

Both invocations of c are valid. In the first case the closure will receive the parameter args with 3 elements ("one", "two", "three"), in the second case the closure will receive the parameter args with 1 element ("1");

++++ What Exception is thrown? MWS

Closure Return Value

Closures always have a return value. The value may be specified via one or more explicit return statement in the closure body, or as the value of the last executed statement if return is not explicitly specified. If the last executed statement has no value (for example, if the last statement is a call to a void method), then null is returned.

There is currently no mechanism for statically declaring the return type of a closure.

References to External Variables

Closures may reference variables external to their own definition. This includes local variables, method parameters, and object instance members. However, a closure may only reference those variables that the compiler can lexically deduce from the physical location of the closure definition within the source file.

Some examples might serve to clarify this. The following example is valid and shows a closure using a method's local variables and a method parameter:

The above code will print out:

Looking at the definition of class A, the closure inside of publicMethod has access to all variables that publicMethod may legally access. This is true whether the variables are local variables, parameters, instance members, or method invocations.

When a closure references variables in this way, they are bound to the closure. At the same time, the variables are still available normally to the enclosing scope, so the closure may read/change any such values, and external scopes may read/change the same variables.

If such a closure is returned from the enclosing scope, the variables bound with the closure also live on. This binding actually occurs when the closure is instantiated. If an object method or instance member is referenced, then a reference to that object is stored within the closure and that referenece is used when the member or method is referenced. If a local variable or parameter is referenced, then the compiler re-writes the local variable or parameter reference so that the reference is taken off the stack and stored in an heap based object that is then referenced within the method body and closure.

++++ This section needs re-wording, I think MWS

It's important to keep in mind that these references only are ever allowed according to the lexical structure available to the compiler (in this case, the A class). This process does not occur dynamically by looking at the call stack. So the following will not work:

The above code is similar to the first example, except that we now have a class B which dynamically instantiates an object of type A and then calls A.publicMethod(). However, in this code the closure within publicMethod() is trying to reference a member from B, and this is not allowed since the compiler cannot statically determine that this is available. Some older languages allowed this sort of reference to work, by dynamically examining the call stack at runtime, but this is disallowed in Groovy.

The Closure Type

All closures defined in Groovy are derived from the type Closure. Each unique closure definition with a Groovy program creates a new unique class which extends Closure. If you wish the specify the type of a closure in a parameter, local variable, or object member instance, then you should use the Closure type.

The exact type of a closure is not defined unless you are explicitly subclasses the Closure class. Using this example:

The exact type of the closure referenced by c is not defined, we know only that it is a subclass of Closure.

Closure creation and invocation

Closures are created implicitly when their surrounding scope encounters them. For example, in the following code two closures are created:

In the above example, closureVar holds a reference to a different closure object than closureVar2. Closures are always implicitly created in this manner - you cannot new a closure programmatically.

Closures may be invoked using one of two mechanisms. The explict mechanism is to use the call() method:

You may also use the implict nameless invocation approach:

If you are looking at the Closure javadoc, you may notice that the call method within the Closure class is defined as:

Despite this method signature, you do not have to manually write code to turn parameters into the Object[] array. Instead, invocations use the normal method argument syntax, and Groovy converts such calls to use an object array:

Both calls above are legal Groovy. However, if you are dealing with a Closure from Java code you will need to create the Object[] array yourself (smile)

Fixing Closure Arguments to Constant Values Via Currying

You can fix the values for one or more arguments to a closure instance using the curry() method from the Closure type. In fact, this action is often referred to as currying in functional programming circles, and the result is generally referred to as a Curried Closure. Curried closures are very useful for creating generic closure definitions, and then creating several curried versions of the original with differing parameters bound to them.

When the curry() method is called on a closure instance with one or more arguments, a copy of the closure is first made. The incoming arguments are then bound permanently to the new closure instance so that the parameters 1..N to the curry() call are bound to the 1..N parameters of the closure. The new curried closure is then returned the caller.

Callers to the new instance will have their invocation parameters bound to the new closure in the N+1 parameter position of the original closure.

A simple example of this would be:

The above code defines a closure c, and then calls c.curry("foo"). This returns a curried closure with the arg1 value permanently bound to the value "foo". On the invocation d("bar"), the "bar" parameter comes into the closure in the arg2 argument. The resulting output would be foo bar.

Special Case: Passing Closures to Methods

Groovy has a special case for defining closures as method arguments to make the closure syntax easier to read. Specifically, if the last argument of a method is of type Closure, you may invoke the method with an explicit closure block outside of the parenthesis. For example, if a class has a method:

Then you may invoke each() with a closure definition outside of the parenthesis:

The more traditional syntax is also available, and also note that in Groovy you can elide parenthesis in many situations, so these two variations are also legal:

The same rule applies even if the method has other arguments. The only restriction is that the Closure argument must be last:

This syntax is only allowed when explicitly defining a closure within the method call. You cannot do this with a variable of type closure:

Comparing Closures to Anonymous Inner Classes

Groovy includes closures because they allow the developer to write more concise and more easily understood code. Where Java developers may use single-method interfaces (Runnable, the Command pattern) combined with anonymous inner classes, Groovy allows you to accomplish the same sort of tasks in a less verbose manner. In addition, closures have fewer constraints than anonymous inner classes and include extra functionality.

Most closures are relatively short, isolated, and anonymous snippets of code that accomplish one specific job. Their syntax is streamlined to make closure definitions very short and easy to read without additional clutter. For example, in Java code you might see code like this for an imaginary GUI system:

The same code in Groovy would look like this:

The Groovy code accomplishes the same task but is much clearer and without extra syntactical clutter. This is the first
rule of Groovy closures - closures are trivially easy to write. In addition, closures may reference any variables in its outer
defining scope without the restrictions of anonymous inner classes - in particular, such variables do not need to be final.
Closures also carry their state around with them, even when they reference local variables and parameters. Closures may also take advantage of Groovy's optional dynamic typing so that you don't have to statically declare all of your closure arguments or return types (in fact, a Groovy closure can take varying numbers of parameters from invocation to invocation).

What Groovy closures lack compared to an approach using Command-like interfaces is the level of static typing involved. A Java interface rigidly enforces what type of objects can be used and the method(s) that may be called in it. In Groovy, all closures type equally as Closure and type checking of arguments (if specified in the closure definition) is deferred until Runtime.

References

You can see more documentation on closures here

  • No labels