Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Functional programming is a style of programming that emphasizes the application of functions to solve computing problems. This is in contrast with imperative programming, which emphasizes changes in state and the execution of sequential commands. If you want use a functional-only programming language, you should consider something like Haskell. If however you like Groovy but want to apply some functional style magic, read on.

Functional Basics

Groovy's functions (like Java's) can be used to define functions which contain no imperative steps, e.g. a factorial function may look like:

Code Block
def fac(n) { n == 0 ? 1 : n * fac(n - 1) }
assert 24 == fac(4)

In Groovy, we gain some slight syntactic sugar over Java in that we can leave out the return statements (the last evaluated expression is the default return value).

You can also define the above factorial function using a Closure:

Code Block
def fac2 = { n -> n == 0 ? 1 : n * call(n - 1) }
assert 24 == fac2(4)

As you can see from the last line, this Closure version gets called in the same way as the function version (although a long hand fac2.call(4) is also supported). The closure version has the advantage that we can pass the fac2 Closure around (e.g. as a parameter) or place it within a data structure.

We can also convert between functions and Closures, e.g.

Code Block
def fac3 = this.&fac
assert 24 == fac3(4)

The fac3 variable will also be a closure.

We can of course start to mix and match functional and imperative coding styles as in this quicksort-like example:

Code Block
def sort(list) {
    if (list.isEmpty()) return list
    anItem = list[0]
    def smallerItems = list.findAll{it < anItem}
    def equalItems = list.findAll{it == anItem}
    def largerItems = list.findAll{it > anItem}
    sort(smallerItems) + equalItems + sort(largerItems)
}

assert [1, 3, 4, 5] == sort([1, 4, 5, 3])
assert [1, 1, 3, 4, 4, 5, 8] == sort([4, 1, 4, 1, 8, 5, 3])
assert ['a', 'b', 'c'] == sort(['c', 'b', 'a'])

Curry functions

You can fix the values for one or more arguments to a closure instance using the curry() method as follows:

Code Block
def joinTwoWordsWithSymbol = { symbol, first, second -> first + symbol + second }
assert joinTwoWordsWithSymbol('#', 'Hello', 'World') == 'Hello#World'

def concatWords = joinTwoWordsWithSymbol.curry(' ')
assert concatWords('Hello', 'World') == 'Hello World'

def prependHello = concatWords.curry('Hello')
// def prependHello = joinTwoWordsWithSymbol.curry(' ', 'Hello')
assert prependHello('World') == 'Hello World'

If you supply one argument to the curry() method you will fix the first argument. If you supply N arguments you will fix arguments 1..N. See reference 1 or Chapter 5 of GINA for further details.

Lazy evaluation

One particular style of functional programming of particular merit is to make use of lazy evaluation. This allows you to define infinite structures (see the next section), devise particularly efficient solutions to certain kinds of problems, and come up with very elegant solutions to otherwise hard problems. The good news is that several parts of Groovy already make use of this style and they typically hide away the hard bits so you don't need to know what magic is happening on your behalf. Here's some examples:

  • XmlSlurper allows arbitrary GPath expressions to be crafted. As you create the expressions, you might think that XML parsing is going on behind the covers pulling XML nodes into and out of lists to match what your expressions are asking for. This is not the case. Instead a lazy representation of your GPath is stored away. When you need to evaluate the final result of a GPath expression, it calculates just what it needs to determine the expressions resulting value. [See chapter 12 of GINA for more information about XmlSlurper.]
  • Groovy's DataSet feature does the some thing for data stored in relational databases. As you build up your dataset queries, no connections or operations to the database are happening under the covers. At the time when you need the result, an optimised query minimising SQL traffic is invoked to return the required result. [See section 10.2 of GINA for more information about DataSets.]

Infinite structures

See reference 2 below for all the details, but to give you a flavour, first you must define some lazy list handling functions, then you can define and use infinite streams. Here is an example:

Code Block
// general purpose lazy list class
class LazyList {
    def car
    private Closure cdr
    LazyList(def car, Closure cdr) { this.car=car; this.cdr=cdr }
    def LazyList getCdr() { cdr ? cdr.call() : null }
    def List take(n) {
        def r = []; def l = this
        n.times { r.add(l.car); l = l.cdr }
        r
    }
    def LazyList filter(Closure pred) {
        if (pred(car)) return pred.owner.cons(car, { getCdr().filter(pred) })
        else return getCdr().filter(pred)
    }
}

// general purpose lazy list function
def cons(val, Closure c) { new LazyList(val, c) }

// now define and use infinite streams
def integers(n) { cons(n, { integers(n+1) }) }
def naturalnumbers = integers(1)
assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.take(10).join(' ')
def evennumbers = naturalnumbers.filter{ it % 2 == 0 }
assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.take(10).join(' ')

If you are not familiar with traditional functional programming terms like cons, car and cdr, you may find the refactored version below (creds to Alexander Kriegisch) somewhat more readable:

Code Block
class LazyList {
    def head
    private Closure tail
    LazyList(def head, Closure tail) { this.head=head; this.tail=tail }
    def LazyList getTail() { tail ? tail.call() : null }
    def List getFirst(n) {
        def result = []; def current = this
        n.times { result.add(current.head); current = current.tail }
        result
    }
    def LazyList filter(Closure matchExpr) {
        if (matchExpr(head))
            return matchExpr.owner.prepend(head, { getTail().filter(matchExpr) })
        else
            return getTail().filter(matchExpr)
    }
}
 
// general purpose lazy list function
def prepend(val, Closure c) { new LazyList(val, c) }
 
// now define and use infinite streams
def integers(n) { prepend(n, { integers(n+1) }) }
def naturalnumbers = integers(1)
assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.getFirst(10).join(' ')
def evennumbers = naturalnumbers.filter{ it % 2 == 0 }
assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.getFirst(10).join(' ')

Using Functional Java

You can also consider using Java libraries which support functional programming, e.g. using Functional Java for an example similar to the evens example above:

Code Block
import fj.data.Stream

// some metaprogramming to make fj mesh well with Groovy
Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) }
Stream.metaClass.getAt = { n -> delegate.index(n) }
Stream.metaClass.getAt = { Range r -> r.collect{ delegate.index(it) } }
Stream.metaClass.asList = { delegate.toCollection().asList() }

def evens = Stream.range(0).filter{ it % 2 == 0 }
assert evens.take(5).asList() == [0, 2, 4, 6, 8]
assert (8..12).collect{ evens[it] } == [16, 18, 20, 22, 24]
assert evens[3..6] == [6, 8, 10, 12]

Or for something a little more adventurous, we can consider calculating primes:

Code Block
import fj.data.Stream
import static fj.data.Stream.cons

// some metaprogramming to make Closures mesh well with fj functions and products
Stream.metaClass.filterTail = { Closure c -> delegate.tail()._1().filter(c as fj.F) }
Stream.metaClass.static.cons = { h, Closure c -> delegate.cons(h, ['_1':c] as fj.P1) }

def sieve(nums) {
  cons nums.head(), { sieve nums.filterTail{ n -> n % nums.head() != 0 } }
}

println sieve(Stream.range(2)).index(100) // => 547

Remember to add functionaljava.jar to your classpath. You will need a version greater than version 2.8 of Functional Java.

Another example:

Code Block
@Grab('org.functionaljava:functionaljava:3.0')
import static fj.data.Array.array
import static fj.data.List.list
import static fj.Show.*
import fj.F

arrayShow(intShow).println(array(1, 2, 3).map({ it + 42 } as F)) // => {43,44,45}
assert array(1, 2, 3).map({ it + 42 } as F).toCollection().toList() == [43, 44, 45]
assert [1, 2, 3].collect{ it + 42 } == [43, 44, 45] // native Groovy for comparison

listShow(intShow).println(list(1, 2, 3).bind({ it % 2 ? list(it * 2) : list() } as F)) // => <2,6>
assert list(1, 2, 3).bind({ it % 2 ? list(it * 2) : list() } as F).toCollection().toList() == [2, 6]
assert [1, 2, 3].collect{ it % 2 ? [it * 2] : [] }.sum() == [2, 6] // native Groovy for comparison

Or with a little bit of metaprogramming like so:

Code Block
import static fj.data.Java.ArrayList_List as asList
fj.data.List.metaClass.toList = { delegate.toCollection().toList() }
List.metaClass.bind = { c -> asList().f(delegate).bind({ asList().f(c(it)) } as F) }
List.metaClass.map = { c -> asList().f(delegate).map(c as F) }

The above examples can be re-written like this:

Code Block
assert [1, 2, 3].bind{ it % 2 ? [it * 2] : [] }.toList() == [2, 6]
assert [1, 2, 3].map{ it + 42 }.toList() == [43, 44, 45]

More Information

See also:

  1. Practically Groovy: Functional programming with curried closures
  2. Infinite Streams in Groovy
  3. Functional Programming Languages
  4. Why Functional Programming Matters
  5. Functional programming in the Java language
  6. Post on functional programming in Java - maybe a tad verbose
  7. FunctionalJ - A library for Functional Programming in Java
  8. Weak versus strong languages, who wins the fight?
  9. Programming Languages:Application and Interpretation
  10. Beyond Groovy 1.0: Groovy goes Lisp
  11. Concurrency with Groovy