- Referential Transparency
- Side effect free
- Declarative vs Imperative
- First class / higher order functions
- Concurrency friendly
- State free
Wikipedia:
a style of building the structure and elements of computer programs, that treats computation as the evaluation of mathematical functions and avoids state and mutable data. Functional programming emphasizes functions that produce results that depend only on their inputs and not on the program state - i.e. pure mathematical functions.
> git clone https://github.com/Bijnagte/functional-groovy-code.git
> cd functional-groovy-code/src/main/groovy
> groovysh
- consistent inputs
- safe sharing
- values not places
- cacheable
Immutability the groovy way
import groovy.transform.Immutable
@Immutable(copyWith = true)
class ImmutableType {
int integer
String string
List list
}
Minimizing moving parts by eliminating mutation
sizeOfList = { list, counter = 0 ->
if (list.size() == 0) {
counter
} else {
sizeOfList(list.tail(), counter + 1)
}
}
sizeOfList(1..100)
=> 100
sizeOfList(1..10000)
=> java.lang.StackOverflowError
Tail call optimization
sizeOfList = { list, counter = 0 ->
if (list.size() == 0) {
counter
} else {
sizeOfList.trampoline(list.tail(), counter + 1)
}
}.trampoline()
sizeOfList(1..10000)
=> 10000
Groovy 2.3 + only
import groovy.transform.TailRecursive
@TailRecursive
long sizeOfList(list, counter = 0) {
if (list.size() == 0) {
counter
} else {
sizeOfList(list.tail(), counter + 1)
}
}
sizeOfList(1..10000)
=> 10000
Closures can be...
assigned to variables
def greet = { greeting, recipient -> "$greeting $recipient" }
greet('hello', 'world')
=> 'hello world'
used in maps
thing = [func: {-> 'called' } ]
thing.func()
=> 'called'
passed as arguments
capitalize = { string -> string.toUpperCase() }
callWithString = { string, function -> function(string) }
callWithString('hello', capitalize)
=> 'HELLO'
callWithString('hello') { it.reverse() }
=> 'olleh'
Methods are functions with a hidden first argument 'this'
@Immutable
class Point {
int x
int y
Point plus(Point other) {
new Point(this.x + other.x, this.y + other.y)
}
}
static Point plus(Point that, Point other) {
new Point(that.x + other.x, that.y + other.y)
}
a = new Point(1, 2)
b = new Point(3, 4)
plus(a, b) == a.plus(b)
class PointFunctions {
static Point minus(Point that, Point other) {
new Point(that.x - other.x, that.y - other.y)
}
}
use(PointFunctions) {
a - b
}
=> Point(-2, -2)
Categories applied globally in the Groovy runtime
int addIntegers(int... integers) {
int result = 0
for (int i = 0; i < integers.size(); i++) {
result += integers[i]
}
result
}
addIntegers(1, 2, 3) == [1, 2, 3].sum()
Alan Perlis:
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.
list = [1, 2, 3, 4, 5, 6]
multiplied = list.collect { it * 2 }
=> [2, 4, 6, 8, 10, 12]
list = [1, 2, 3, 4, 5, 6]
filtered = list.findAll { it % 2 == 0 }
=> [2, 4, 6]
list = [1, 2, 3, 4, 5, 6]
list.inject(1) { a, b -> a * b }
=> 720
list.inject { a, b -> a * b }
=> 720
Partial application of functions
people = ['Jane', 'Dave', 'Wendy']
nth = { sequence, index -> sequence[index] }
nthPerson = nth.curry(people)
nthPerson(2)
=> 'Wendy'
second = nth.rcurry(1)
second(people)
=> 'Dave'
Combining simple functions to build more complicated ones
reverse = { it.reverse() }
capitalize = { it.toUpperCase() }
third = { it[2] }
input = ['hello', 'there', 'people']
thirdReverse = third >> reverse
thirdReverse(input)
=> 'elpoep'
reverseThird = third << reverse
reverseThird(input)
=> 'hello'
process = third >> reverse >> capitalize
process(input)
=>'ELPOEP'
Caching repeatable results
add = { a, b ->
println "$a * $b = ${a * b}"
a * b
}
memoizedAdd = add.memoize()
import groovy.transform.Memoized
@Memoized
int memoizedMultiply(int a, int b) {
println "$a * $b = ${a * b}"
a * b
}
- Ability to reason about increases
- Testability
- Safer concurrency
- Code reuse
- Performance optimizations