14. Imperative vs. Declarative
Imperative: how to achieve our goal
Take the next customer from a list.
If the customer lives in Spain, show their details.
If there are more customers in the list, go to the beginning
Declarative: what we want to achieve
Show customer details of every customer living in Spain
15. Imperative vs. Declarative
Functional programming is like describing
your problem to a mathematician.
Imperative programming is like giving
instructions to an idiot.
arcus, #scheme on Freenode
16. Functional Programming
with Groovy?
is an imperative language,
but we still can apply functional principles
It's basically a programmer's choice
17. Immutability
Simple
Immutable objects can only be in exactly
one state, the state in which it was created
Always consistent
Less prone to errors and more secure
Immutable objects can be shared freely
Freedom to cache
Inherently thread-safe
18. Immutability
NO: (even being a name rebind and not a real update)
book = 'Fooled by Randomness'
book = "$book - Nassim Taleb"
book = "$book (2001)"
assert 'Fooled by Randomness - Nassim Taleb (2001)' == book
YES:
book = 'Fooled by Randomness'
bookWithAuthor = "$book - Nassim Taleb"
completeBook = "$bookWithAuthor (2001)"
assert 'Fooled by Randomness - Nassim Taleb (2001)' == completeBook
19. Immutability
NO:
years = [2001, 2002]
years << 2003
years += [2004, 2005]
assert [2001, 2002, 2003, 2004, 2005] == years
YES:
years = [2001, 2002]
allYears = years + 2003 + [2004, 2005]
assert [2001, 2002, 2003, 2004, 2005] == allYears
20. Immutability
def list = ['Gr', 'vy']
NO:
list.addAll 1, 'oo'
assert list == ['Gr', 'oo', 'vy']
YES:
assert list.plus(1, 'oo') == ['Gr', 'oo', 'vy']
assert list == ['Gr', 'vy']
21. Immutability
def list = [1, 2, 2, 3]
NO:
list.removeAll 2
assert list == [1, 3]
YES:
assert list.minus(2) == [1, 3]
assert list == [1, 2, 2, 3]
26. Immutability Class
@Immutable class Coordinates {
Double latitude, longitude
}
def c1 = new Coordinates(latitude: 48.824068,
longitude: 2.531733)
def c2 = new Coordinates(48.824068, 2.531733)
assert c1 == c2
27. Higher-Order Functions
First-Class Citizen
Can be stored in variables
Can be passed as function parameter
Can be returned from functions
Higher-Order Functions (First-Class Functions)
Functions that take other functions
as arguments or return them as results
28. Closures
def closure = { 'Hello world!' }
assert closure() == 'Hello world!'
def sum = { a, b -> a + b }
assert sum(2,3) == 5
def square = { it * it }
assert square(9) == 81
final BASE = 1000
def salary = { variable -> BASE + variable }
assert salary(500) == 1500
29. Turn Methods into Closures
def salary(variable) {
final BASE = 1000
BASE + variable
}
assert salary(500) == 1500
def salaryClosure = this.&salary
assert salaryClosure(500) == 1500
32. Currying
given: ƒ: (X x Y) -> Z
then: curry(ƒ): X -> (Y -> Z)
Takes a function with a particular
number of parameters and returns a
function with some of the parameter
values fixed, creating a new function
39. findAll()
NO:
def result = []
[1, 2, 3, 4].each {
if (it > 2) {
result << it
}
}
assert result == [3, 4]
YES:
assert [1, 2, 3, 4].findAll{ it > 2 } == [3, 4]
40. collect()
NO:
def result = []
[1, 2, 3].each {
result << it * 2
}
assert result == [2, 4, 6]
YES:
assert [1, 2, 3].collect{ it * 2 } == [2, 4, 6]
41. inject()
NO:
def total = 0
[1, 2, 3].each {
total += it
}
assert total == 6
YES:
def total = [1, 2, 3].inject(0) { acc, n ->
acc + n
}
assert total == 6
42. find()
NO:
def result
try {
[1, 2, 3].each {
if (it > 1) {
result = it
throw new Exception() // monstrous
}
}
} catch(exception) { }
assert result == 2
YES:
assert [1, 2, 3].find{ it > 1 } == 2
43. max()
@TupleConstructor // import groovy.transform.*
class Person {
String name
Integer age
}
def person1 = new Person('Arturo', 26)
def person2 = new Person('Luis', 61)
def person3 = new Person('Laura', 19)
def family = [] << person1 << person2 << person3
assert family.max{ it.age }.age == 61
assert family.collect{ it.age }.max() == 61
assert family*.age.max() == 61
44. Refactoring
def exists = false
family.each { person ->
if (person.age > 60) {
exists = true
}
}
assert exists == true
def exists = family.inject(false) { found, person ->
if (person.age > 60) {
found = true
}
return found
}
assert exists == true
assert family.any{ it.age > 60 } == true
45. Combinator Functions
@TupleConstructor // import groovy.transform.*
class Person {
String name
String lastname
Integer age
}
def rafa = new Person('Rafael', 'Luque', 36)
def marcin = new Person('Marcin', 'Gryszko', 34)
def arturo = new Person('Arturo', 'Herrero', 26)
def osokers = [] << rafa << marcin << arturo << rafa
assert osokers.unique(false)
.findAll{ it.age > 30}
.sort{ it.lastname } == [marcin, rafa]
assert osokers == [rafa, marcin, arturo, rafa]
46. Combinator Functions
// Procedural style
def count = 0
for (i in (1 .. 1000)) {
if (i % 2) {
count += ("$i".size())
}
}
assert count == 1445
// Functional style
def count = (1 .. 1000).findAll{ it % 2 }
.collect{ "$it" }
.inject(0) { sum, num ->
sum + num.size()
}
assert count == 1445
47. Lazy Evaluation
Only does as much work as necessary
Delays the evaluation of the expression
until it's needed
CPU efficient
The value is not calculated or assigned
until the value is requested
Manage potentially infinite data structures
Only a manageable subset of the data
will actually be used
48. Lazy Evaluation
class Person {
@Lazy String name = 'Arturo'
}
def person = new Person()
assert !(person.dump().contains('Arturo'))
assert person.name.size() == 6
assert person.dump().contains('Arturo')
49. Lazy Evaluation
class Get {
String url
@Lazy URL urlObj = { url?.toURL() }()
@Lazy(soft=true) String text = urlObj?.text
}
def get = new Get(url: 'http://arturoherrero.com')
assert get.url == 'http://arturoherrero.com'
assert get.dump().contains('text=null')
assert get.dump().contains('urlObj=null')
assert get.urlObj.protocol == 'http'
assert get.urlObj.host == 'arturoherrero.com'
assert get.text.contains('Arturo Herrero')
57. Tail Call Optimization
3 techniques:
The compiler transform the recursion into a loop
λ
Let the JVM recognize the recursion and eliminate it
λ
Transform the recursion into iterative by hand
λ
58. Tail Call Optimization
3 techniques:
The compiler transform the recursion into a loop
λ
Let the JVM recognize the recursion and eliminate it
λ
Transform the recursion into iterative by hand
λ
really?
59. Tail Call Optimization
def factorial
factorial = { n ->
n == 1 ? 1 :
n * factorial(n - 1)
}
factorial(1000)
60. Tail Call Optimization
rflow
kO ve
def factorial S tac
factorial = { n ->
n == 1 ? 1 :
n * factorial(n - 1)
}
factorial(1000)
61. Tail Call Optimization
def factorial
factorial = { n, BigInteger acc = 1 ->
n == 1 ? acc :
factorial(n - 1, n * acc)
}
factorial(1000)
62. Tail Call Optimization
rflow
kO ve
def factorial S tac
factorial = { n, BigInteger acc = 1 ->
n == 1 ? acc :
factorial(n - 1, n * acc)
}
factorial(1000)
63. Tail Call Optimization
def factorial
factorial = { n, BigInteger acc = 1 ->
n == 1 ? acc :
factorial.trampoline(n - 1, n * acc)
}.trampoline()
factorial(1000)
64. Trampolining
def even, odd
even = { x -> x == 0 ? true :
odd.trampoline(x - 1) }.trampoline()
odd = { x -> x == 0 ? false :
even.trampoline(x - 1) }.trampoline()
assert even(1000) == true