Functional Groovy

"Wouldn't it be groovy if [ Java ]

could do this or that" ~ beta devs

Commit Graph & History

Guillaume Laforge

Apache

Official docs quite good!

The Basics

Valid Groovy syntax?

x.java ==> x.groovy

* groovy-all.jar must be on the classpath

Hello World

println "Hello World!"

Any executable code with no class or method is thrown into a temp class' main, compiled, and run

No semicolons

it's taboo ;)

def (dynamic type)

Never for APIs; seldom else

eg. Closures, Slurpers

def data = 1
data = 'string value'
assert data += 3 == 'string value3' // please noooo!!!!

auto returns last eval

def addOne = { it + 1 }

//or

def addOne = { int num -> num + 1 }

 

assert addOne(1) == 2

GString

String fullName = "${lastName}, ${firstName}"

Maps and Lists

Map hashMap = [:]

List arrayList = []

Set sortedSet = [] as SortedSet

assert [1] as Set == [1, 1] as Set


Elvis operator

String name = person.name() ?: 'NO NAME'

Safe null referencing

String name = 
       person?.sister?.getName() ?: 'NO SISTER'

More good stuff

The Functional

Topics

  1. Closures as Objects
  2. Curry
  3. Recursion & stack overflows
  4. Immutable data
  5. Side effect-free list manipulations
    • map
    • fold/reduce
    • filter

1. Closures

Closure returnClr = { int arg1 ->
    def doubleIt = { arg1 * 2 }
    return doubleIt // return a closure
}

assert returnClr(2)() == 4


Closure testRun = { def data, Closure predicate ->
    return predicate(data)
}
def isEven = { it % 2 == 0 }

assert testRun(2, isEven) == true  // pass in a closure
assert testRun(3, isEven) == false // pass in a closure

def sumAll = { int ...x -> x.sum() }
assert sumAll(1, 2, 3) == 6

Note: interop with lambdas, but still more powerful

1.b Closures on methods

Closure twoArgsClr = { String a, String b ->
    return (a + b)
}
assert twoArgsClr.rcurry('a')('b') == 'ba'


class Aaa {
    static String twoArgs(String a, String b) {
        return (a + b)
    }
}
assert Aaa.&twoArgs.rcurry('a')('b') == 'ba' // .& turns any class method into a closure

2. Curry

.curry, .rcurry, .ncurry

def addNumbers = { x, y -> x + y }
def addOne = addNumbers.curry(1) // returns a closure kinda like { 1, y -> ... }
assert 5 == addOne(4)


def testIt = { Closure predicate, def val ->
    predicate(val)
}
def predicateEven = { it % 2 == 0 }
def testEven = testIt.curry(predicateEven)

assert testEven(2) == true

3. Recursion

@groovy.transform.TailRecursive
long sizeOfList(list, counter = 0) {
    if (list.size() == 0) {
        counter
    } else {
       sizeOfList(list.tail(), counter + 1) 
    }
}

// Find the size of a List with 1000 items in it
// Without @TailRecursive a StackOverFlowError is thrown.
assert sizeOfList(1..10000) == 10000
assert sizeOfList(1..10000) == (1..10000).size()


def sizeList
sizeList = { list, counter = 0 ->
    if (list.size() == 0) {
        counter
    } else {
        sizeList.trampoline(list.tail(), counter + 1)
    }
}.trampoline()

assert sizeList(1..10000) == 10000

4. Immutable

@groovy.transform.Immutable
class Person {
    String firstName
    String lastName
    Integer age
    Collection hobbies
}

List hobbies = ['singing']
Person p1 = new Person('Donald', 'Trump', 68, hobbies)

p1.age = 99 // throws ReadOnlyPropertyException
p1.hobbies << 'yachting' // throws UnsupportedOperationException

hobbies << 'dancing'
assert hobbies.size() > p1.hobbies.size()
assert !hobbies.is(p1.hobbies) // deep cloning of Objects

List list1 = [1,2,3].asImmutable()

Notes: - a safe strategy to .memoize stateless functions!

            - see @Immutable(copyWith = true)

 

5. List Manipulations/Loops

  1. Map
    • .collect
    • *.
  2. Fold/Reduce
    • .inject
    • .sum, .max, etc
  3. Filter
    • .find
    • .findAll, .grep, takeWhile, dropWhile
  4. Loops
    • .each, .eachWithIndex
    • .times

5.1 Map (aka collect, *)

List list1 = [1, 2, 3]

assert list1.collect { it.toString() } == ['1', '2', '3']

assert list1*.toString() == ['1', '2', '3'] // uncommon


new File('/tmp/').listFiles()*.delete() // uncommon
new File('/tmp/').listFiles().each { it.delete() }

5.2 Fold (aka inject)

Integer sum = (1..4).inject(0) { result, i -> result + i }
assert 10 == sum

Boolean anyFound = [1, 2, 3].inject(false) { result, i -> result || i < 2 }
assert anyFound == true

Boolean allFound = [1, 2, 3].inject(true) { result, i -> result && i < 3 }
assert allFound == false


Map data = ['Bob': 'Smith', 'Jill': 'Smith']
Map report = data.inject([:].withDefault{ 0 }) { result, e ->
    result[e.value] += 1
    return result
}
assert ['Smith': 2] == report

The Advanced

An expert, you will become

Fin

Functional Groovy

By crazy4groovy

Functional Groovy

  • 6,024