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
- Closures as Objects
- Curry
- Recursion & stack overflows
- Immutable data
- 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
- Map
- .collect
- *.
- Fold/Reduce
- .inject
- .sum, .max, etc
- Filter
- .find
- .findAll, .grep, takeWhile, dropWhile
- 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,300