On Github Bijnagte / functional-groovy-slides
Dylan Bijnagte
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
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 }