functional programming with groovy
Post on 04-Dec-2014
12.411 Views
Preview:
DESCRIPTION
TRANSCRIPT
Functional Programmingwith Groovλ
Working at OSOCO
Small but outstanding software development shop
Groovy and Grails hackers
on EC2 cloud nine
TDD mantra singers
Quality preachers
(define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))
LISt Processing
( (factorial ) ( ( ) ( (factorial ( )))))
Lots of Insipid Stupid Parentheses
Code Complete
The Pragmatic Programmer
Structure and Interpretation of Computer Programs
A language that doesn't affect the way you think
about programming, is not worth knowing
Alan Perlis
Functional Programming
Function
Functional Programming
Avoiding Mutable State Side-Effect-Free Functions Referential Transparency First-Class Citizens Higher-Order Functions Lambdas and Closures Lazy Evaluation Recursion
λλ
λλ
λλ
λλ
Why Functional Programming?
Referential transparency Unit testing Debbuging Parallelization Modularity and composition Increases the quality of code Abstractions
λλ
λλ
λλ
λ
OOP vs. FP
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
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
Functional Programmingwith Groovy?
is an imperative language,
but we still can apply functional principles
It's basically a programmer's choice
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
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
Immutability
NO:years = [2001, 2002]years << 2003years += [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
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']
Immutability
def list = [1, 2, 2, 3]
NO:
list.removeAll 2assert list == [1, 3]
YES:
assert list.minus(2) == [1, 3]assert list == [1, 2, 2, 3]
Immutability
def list = ['Scala', 'Groovy', 'Java']
NO:
sortedList = list.sort()assert sortedList == ['Groovy', 'Java', 'Scala']assert list == ['Groovy', 'Java', 'Scala']
YES:
sortedList = list.sort(false)assert sortedList == ['Groovy', 'Java', 'Scala']assert list == ['Scala', 'Groovy', 'Java']
Immutability
def list = ['Java', 'Groovy', 'Java']
NO:
uniqueList = list.unique()assert uniqueList == ['Java', 'Groovy']assert list == ['Java', 'Groovy']
YES:
uniqueList = list.unique(false)assert uniqueList == ['Java', 'Groovy']assert list == ['Java', 'Groovy', 'Java']
Immutability
def list = ['Java', 'Groovy']
NO:
reverseList = list.reverse(true)assert reverseList == ['Groovy', 'Java']assert list == ['Groovy', 'Java']
YES:
reverseList = list.reverse()assert reverseList == ['Groovy', 'Java']assert list == ['Java', 'Groovy']
Immutability Collection
def list = ['Groovy', 'Java'].asImmutable()assert 'Groovy' == list.first()
try { list.add 'Scala' // Cannot add item} catch (e) { assert e instanceof UnsupportedOperationException}
try { list.remove 'Java' // Cannot remove item} catch (e) { assert e instanceof UnsupportedOperationException}
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
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
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
Turn Methods into Closures
def salary(variable) { final BASE = 1000 BASE + variable } assert salary(500) == 1500
def salaryClosure = this.&salary assert salaryClosure(500) == 1500
Closures Composition
def minutesToSeconds = { it * 60 }def hoursToMinutes = { it * 60 }def daysToHours = { it * 24 } def hoursToSeconds = minutesToSeconds << hoursToMinutesdef daysToSeconds = hoursToSeconds << daysToHours
assert daysToSeconds(1) == 86400
Closures Composition
def upper = { it.toUpperCase() }def firstLetter = { it.charAt(0) }
def words = ["Don't", "Repeat", "Yourself"]def acronym = words.collect(firstLetter >> upper).join()
assert acronym == 'DRY'
Currying
given: ƒ: (X x Y) -> Z
then: curry(ƒ): X -> (Y -> Z)
Takes a function with a particularnumber of parameters and returns afunction with some of the parametervalues fixed, creating a new function
Currying
def modulus = { mod, num -> num % mod } assert modulus(2, 5) == 1 assert modulus(3, 5) == 2
def mod2 = modulus.curry(2) assert mod2(5) == 1 def mod3 = modulus.curry(3) assert mod3(5) == 2
Currying
def bill = { amount, currency -> "$amount $currency" } assert bill(1000, '$') == '1000 $' assert bill(1000, '€') == '1000 €'
def billInDollars = bill.rcurry('$') assert billInDollars(1000) == '1000 $'
def billInEuros = bill.rcurry('€') assert billInEuros(1000) == '1000 €'
Currying
def joinWithSeparator = { one, sep, two -> one + sep + two }
def joinWithAmpersand = joinWithSeparator.ncurry(1, '&')
assert joinWithAmpersand('a', 'b') == 'a&b'
Classic Operations on Functional Data Types
list filter
map
fold
Classic Operations on Functional Data Types
list findAll
collect
inject
Classic Operations on Functional Data Types
list any
sort
sum
every
min
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]
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]
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
find()NO:
def resulttry { [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
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
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
@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]
Combinator Functions
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
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
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')
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')
Lazy Evaluation
groovy.sql.DataSet.DataSet groovy.util.XmlSlurper
@Singleton(lazy=true) class Util { Integer count(text) { text.size() } } assert 6 == Util.instance.count('Arturo')
Infinite structures
class LazyList { ...}
def naturalnumbers = integers(1)assert '1 2 3 4 5 6' == naturalnumbers.take(6).join(' ')
def evennumbers = naturalnumbers.filter{ it % 2 == 0 }assert '2 4 6 8 10 12' == evennumbers.take(6).join(' ')
Infinite structures
@Grab('org.functionaljava:functionaljava:3.0')import fj.data.Stream
Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) }
Stream.metaClass.asList = { delegate.toCollection().asList() }
def evens = Stream.range(1).filter{ it % 2 == 0 }assert [2, 4, 6, 8, 10, 12] == evens.take(6).asList()
Recursive factorial(6) 6 * factorial(5) 6 * (5 * factorial(4)) 6 * (5 * (4 * factorial(3))) 6 * (5 * (4 * (3 * factorial(2)))) 6 * (5 * (4 * (3 * (2 * factorial(1))))) 6 * (5 * (4 * (3 * (2 * 1)))) 6 * (5 * (4 * (3 * 2))) 6 * (5 * (4 * 6)) 6 * (5 * 24) 6 * 120 720
Recursive factorial(6) 6 * factorial(5) 6 * (5 * factorial(4)) 6 * (5 * (4 * factorial(3))) 6 * (5 * (4 * (3 * factorial(2)))) 6 * (5 * (4 * (3 * (2 * factorial(1))))) 6 * (5 * (4 * (3 * (2 * 1)))) 6 * (5 * (4 * (3 * 2))) 6 * (5 * (4 * 6)) 6 * (5 * 24) 6 * 120 720
Tail-Recursive factorial(6, 1) factorial(5, 6) factorial(4, 30) factorial(3, 120) factorial(2, 360) factorial(1, 720)
Tail-Recursive factorial(6, 1) factorial(5, 6) factorial(4, 30) factorial(3, 120) factorial(2, 360) factorial(1, 720)
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
λλ
λ
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?
Tail Call Optimization
def factorial
factorial = { n -> n == 1 ? 1 : n * factorial(n - 1) }
factorial(1000)
Tail Call Optimization
def factorial
factorial = { n -> n == 1 ? 1 : n * factorial(n - 1) }
factorial(1000)
Stack Overflow
Tail Call Optimization
def factorial
factorial = { n, BigInteger acc = 1 -> n == 1 ? acc : factorial(n - 1, n * acc) }
factorial(1000)
Tail Call Optimization
def factorial
factorial = { n, BigInteger acc = 1 -> n == 1 ? acc : factorial(n - 1, n * acc) }
factorial(1000)
Stack Overflow
Tail Call Optimization
def factorial
factorial = { n, BigInteger acc = 1 -> n == 1 ? acc : factorial.trampoline(n - 1, n * acc) }.trampoline()
factorial(1000)
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
Memoization
def fibonacci
fibonacci = { n -> n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2) }
fibonacci(35) // 9.935 seconds
Memoization
def fibonacci
fibonacci = { n -> n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2) }.memoize()
fibonacci(35) // 0.002 seconds
def plus = { a, b -> sleep 1000; a + b }.memoize()
assert plus(1, 2) == 3 // after 1000ms assert plus(1, 2) == 3 // return immediately assert plus(2, 2) == 4 // after 1000ms assert plus(2, 2) == 4 // return immediately def plusAtLeast = { ... }.memoizeAtLeast(10) def plusAtMost = { ... }.memoizeAtMost(10) def plusBetween = { ... }.memoizeBetween(10, 20)
Memoization
“Functional” is more a way of thinkingthan a tool set
Neal Ford
Be a craftsman
Thank you! @ArturoHerrero
Join us at OSOCO
top related