Thursday, May 17, 2012

function(groovy) #3


function(groovy) #3

If all you have is a hammer, everything looks like a nail.

Abstract: The Groovy closure can be the return value from a method or another closure. This idea eases us into the world of higher order functions: functions that accept functions as parameters or return functions as their result. Functions as parameters is the key feature of the next blog. Functions as return values introduces the notion of currying and increasing the adoption of functions with only a single parameter.

Ken Barclay
kenbarc@googlemail.com

Previous:           The Groovy closure
Resources:        Chapter 12 Higher Order Functions

Advanced closures

A single function definition can effectively define a complete family of functions. Having defined the parent function, we are able to specialize from this single parent a number of related functions. To achieve this we use the notion of currying, named after the mathematician Haskell Curry. This idea is generally unfamiliar to the imperative programmer but is one worth getting acquainted with.

Example 3.1 demonstrates how the Closure class method curry is used to achieve the effect of currying. Closure multiply expects two parameters and returns their product. If we curry this function with the value 2, then we have created a new function which doubles its single parameter. Sensibly we choose to refer to this as twice. Equally, if we curry multiply with the value 4, then we have the closure quadruple. Both are functions of one parameter and can be used directly, or are candidates for use with any other function.

Example 3.1: Curried closure

    // multiply:: Integer * Integer -> Integer
final multiply = { final int x, final int y -> x * y }

    // twice:: Integer -> Integer
final twice = multiply.curry(2)
    // quadruple:: Integer -> Integer
final quadruple = multiply.curry(4)


assert twice(5) == 10
assert quadruple(5) == 20

The type signature informs us that multiply is a function which when given a pair of integers will return their product. If then we were able to call function multiply and give only one actual parameter, then we would get a result which is itself another function. For example, if we could call multiply with the parameter 2, then effectively the first formal parameter x is replaced throughout the definition for multiply with this value. Further, since the value for the parameter x is now specified, then this formal parameter can be removed from the definition. We then effectively produce the function:

{ final int y -> return 2 + y }

one that takes a single numeric parameter and returns 2 times its value. This function is the value bound to the identifier twice.

Since a Groovy closure is an object and a Groovy closure can return a closure, then currying can be achieved through another route. In Example 3.2 we have defined a closure add which takes a single parameter x and returns a closure. The returned closure is defined both in terms of its own parameter (y) as well as x, sometimes described as a free variable when used in this manner.

Example 3.2: Alternative currying

    // add:: Integer -> Integer -> Integer
final add = { final int x ->
  return { final int y -> x + y }
}

    // successor:: Integer -> Integer
final successor = add(1)

assert successor(5) == 6
assert successor(successor(5)) == 7

In Example 3.2 the call add(1) delivers a closure that is bound to the successor identifier. In that closure the free variable x has been fixed as the value 1.

Interestingly function add expects a single parameter and delivers a function. That function also expects a single parameter and delivers some result. As shown we are able to support currying without using the curry method. It also moves us to adopting the notion of functions only ever taking one parameter.

Why? Well first we get currying for free. Second, it greatly enhances our capacity to compose functions. Function composition in the previous blog expected them to operate with only a single parameter.

Suppose we want to develop a curried version of a binary function f which is uncurried and of the type A * B -> C. This binary function f expects its parameters as a pair but its curried version will apply them sequentially. We therefore have to form them into a pair before applying f to them. The function C does exactly this:

    // C:: (A * B -> C) -> A -> B -> C
final C = { f -> { a -> { b -> f(a, b) }}}

Again, the signature reveals what is happening. Function C expects a function as its first parameter. That parameter function has the signature A * B -> C, an uncurried binary function. Function C returns the curried function with the signature A -> B -> C, as required. We can use C where we need to transform an uncurried binary function into its curried form.

            Example 3.3 introduces function C and its complementary function U. Using U we convert the curried mul function into it uncurried equivalent, mulU. Using C we transform the uncurried mulU function into its curried equivalent, mulC.

Example 3.3: Curried/uncurried transformation

    // mul:: Integer -> Integer -> Integer
final mul = { final int x -> { final int y -> x * y }}

    // U:: (A -> B -> C) -> (A * B) -> C
final U = { final f -> { final a, final b -> f(a)(b) }}

    // mulU:: (Integer * Integer) -> Integer
final mulU = U(mul)

assert mul(3)(4) == 12
assert mulU(3, 4) == 12
assert U(mul)(3, 4) == 12


    // C:: (A * B -> C) -> A -> B -> C
final C = { final f -> { final a -> { final b -> f(a, b) }}}

    // mulC:: Integer -> Integer -> Integer
final mulC = C(mulU)

assert mulC(3)(4) == 12

Various factory design patterns are used throughout OO-land. The earlier examples showed currying as a mechanism for developing a family of functions from a single definition. In effect currying is a form of factory for functions.

Many of the emerging examples do not exploit Groovy meta-programming capabilities. They also make ever greater demands on the types of quantities, including our function parameter and return types. This work makes a strong case for a Groovy static type checker.




No comments: