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:
Post a Comment