Using Functions as Objects with Closures

While writing helper code for your application, you may find it handy to treat a function as an object called a closure.

It lets you to define a function you can store in a variable, accept as a function parameter, pass into another function as an argument, and later invoke on-demand, passing appropriate arguments as needed.

For example, consider an application that must support different strategies for calculating sales tax on an order's line items. The Order_c object's computeTaxForOrder() function shown below declares a taxStrategyFunction parameter of type Closure to accept a tax strategy function from the caller. At an appropriate place in the code, it invokes the function passed-in by applying parentheses to the parameter name, passing along any arguments.
// Object function on Order_c object
// Float computeTaxForOrder(Closure taxStrategyFunction)
Float totalTax = 0
// Iterate over order line items and return tax using 
// taxStrategyFunction closure passed in
def orderLines = OrderLinesCollection_c
orderLines.reset()
while (orderLines.hasNext()) {
  // Invoke taxStrategyFunction() passing current line's LineTotal_c
  def currentLine = orderLines.next()
  totalTax += taxStrategyFunction(currentLine.LineTotal_c)
}
return totalTax

In one territory ABC, imagine that amounts under 25 euros pay 10% tax while items 25 euros or over pay 22%. In a second territory DEF, sales tax is a flat 20%. We could represent these two tax computation strategies as separate function variables as shown below. The closure is a function body enclosed by curly braces that has no explicit function name. By default the closure function body accepts a single parameter named it that will evaluate to null if no parameter is passed at all when invoked. Here we've saved one function body in the variable named taxForTerritoryABC and another in the variable taxForTerritoryDEF.

def taxForTerritoryABC = { return it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { return it * 0.20 } 
When the function body is a one-line expression, you can omit the return keyword as shown below, since Groovy returns the last evaluated expression as the function return value if not explicitly returned using the return statement.
def taxForTerritoryABC = { it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { it * 0.20 } 
The code inside each anonymous function body is not executed until later when it gets explicitly invoked. With the code in a variable, we can pass that variable as an argument to an object function like the Order_c object's computeTaxForOrder() as shown below. Here we're calling it from a Before Insert trigger on the Order_c object:
// Before Insert trigger on Order_c
def taxForTerritoryABC = { it * (it < 25 ? 0.10 : 0.22) }
// Assign the value of TotalTax_c field, using the taxForTerritoryABC
// function to compute the tax for each line item of the order.
TotalTax_c = computeTaxForOrder(taxForTerritoryABC)

If you don't like the default name it for the implicit parameter passed to the function, you can give the parameter an explicit name you prefer using the following "arrow" (->) syntax. The parameter name goes on the left, and the body of the function on the right of the arrow:

def taxForTerritoryABC = { amount -> amount * (amount < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { val -> val * 0.20 } 

The closure is not limited to a single parameter. Consider the following slightly different tax computation function on the Order_c object named computeTaxForOrderInCountry(). It accepts a taxStrategyFunction that it invokes with two arguments: an amount to be taxed and a country code.

// Object function on Order_c object
// BigDecimal computeTaxForOrderInCountry(Closure taxStrategyFunction) {
BigDecimal totalTax = 0
// Iterate over order line items and return tax using 
// taxStrategyFunction closure passed in
def orderLines = OrderLinesCollection_c
orderLines.reset()
while (orderLines.hasNext()) {
  // Invoke taxStrategyFunction() passing current line's LineTotal_c
  // and the CountryCode_c field value from the owning Order_c object
  def currentLine = orderLines.next()
  totalTax += taxStrategyFunction(currentLine.LineTotal_c,
                                  currentLine.Order_c.CountryCode_c)
}
return totalTax
This means the closure you pass to computeTaxForOrderInCountry must declare both parameters and give each a name as shown in the example below. Notice that the function body can contain multiple lines if needed.
def taxForTerritoryABC = { amount, countryCode ->
                           if (countryCode == 'IT') {
                             return amount * (amount < 25 ? 0.10 : 0.22)
                           }
                           else {
                             return amount * (amount < 50 ? 0.12 : 0.25)
                           }
                         }
There's no requirement that you store the closure function in a local variable before you pass it into a function. You can pass the closure directly inline like this:
// Before Insert trigger on Order_c: Assign TotalTax_c
// using a flat 0.22 tax regardless of countryCode
TotalTax_c = computeTaxForOrderInCountry( { amount, country -> return 0.22 } )
In this situation, to further simplify the syntax, Groovy allows omitting the extra set of surrounding parentheses like this:
TotalTax_c = computeTaxForOrderInCountry{ amount, country -> return 0.22 }
Many built-in collection functions — described in more details in the following sections — accept a closure to accomplish their job. For example, the findAll() function shown below finds all email addresses in the list that end with the .edu suffix.
def recipients = ['sjc@example.edu','dan@example.com',
                  'spm@example.edu','jim@example.org']
def eduAddreses = recipients.findAll{ it?.endsWith('.edu') }
Finally, in order to define a closure that accepts no parameters and should raise an error if any parameter is passed to it, you must use the arrow notation without mentioning any parameters on the left side of the arrow like this:
def logCurrentTime = { -> println("Current time is ${now()}") } 
Some later code that invokes this closure by name by appending parentheses like this will succeed because it is passing no arguments:
// Invoke the closure's function body with no arguments
logCurrentTime()
However, an attempt to pass it an argument will fail with an error:
// This will FAIL because the closure demands no arguments!
logCurrentTime(123)