Functional Programming with Groovλ
@ArturoHerrero http://arturoherrero.com
Working at OSOCO Small but outstanding software development shop Groovy and Grails hackers on EC2 cloud nine TDD mantra singers Quality preachers
LISt Processing (define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))
Lots of Insipid Stupid Parentheses ( (factorial ) ( ( ) ( (factorial ( )))))
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 Programming with 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 << 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
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 2 assert 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 << hoursToMinutes def 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 particular number of parameters and returns a function with some of the parameter values 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 every sort min sum
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 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
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
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]
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 rflow kO ve def factorial S tac factorial = { n -> n == 1 ? 1 : n * factorial(n - 1) } factorial(1000)
Tail Call Optimization def factorial factorial = { n, BigInteger acc = 1 -> n == 1 ? acc : factorial(n - 1, n * acc) } factorial(1000)
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)
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
Memoization 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)
“Functional” is more a way of thinking than a tool set Neal Ford
Be a craftsman
Thank you! @ArturoHerrero Join us at OSOCO

Functional Programming with Groovy

  • 1.
  • 2.
  • 3.
    Working at OSOCO Smallbut outstanding software development shop Groovy and Grails hackers on EC2 cloud nine TDD mantra singers Quality preachers
  • 4.
    LISt Processing (define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))
  • 5.
    Lots of InsipidStupid Parentheses ( (factorial ) ( ( ) ( (factorial ( )))))
  • 6.
    Code Complete The PragmaticProgrammer Structure and Interpretation of Computer Programs
  • 8.
    A language thatdoesn't affect the way you think about programming, is not worth knowing Alan Perlis
  • 9.
  • 10.
  • 11.
    Functional Programming Avoiding Mutable State λ Side-Effect-Free Functions λ Referential Transparency λ First-Class Citizens λ Higher-Order Functions λ Lambdas and Closures λ Lazy Evaluation λ Recursion λ
  • 12.
    Why Functional Programming? Referential transparency λ Unit testing λ Debbuging λ Parallelization λ Modularity and composition λ Increases the quality of code λ Abstractions λ
  • 13.
  • 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 Functionalprogramming 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 Immutableobjects 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 beinga 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]
  • 22.
    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']
  • 23.
    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']
  • 24.
    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']
  • 25.
    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 }
  • 26.
    Immutability Class @Immutable classCoordinates { 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 intoClosures def salary(variable) { final BASE = 1000 BASE + variable } assert salary(500) == 1500 def salaryClosure = this.&salary assert salaryClosure(500) == 1500
  • 30.
    Closures Composition def minutesToSeconds= { it * 60 } def hoursToMinutes = { it * 60 } def daysToHours = { it * 24 } def hoursToSeconds = minutesToSeconds << hoursToMinutes def daysToSeconds = hoursToSeconds << daysToHours assert daysToSeconds(1) == 86400
  • 31.
    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'
  • 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
  • 33.
    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
  • 34.
    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 €'
  • 35.
    Currying def joinWithSeparator ={ one, sep, two -> one + sep + two } def joinWithAmpersand = joinWithSeparator.ncurry(1, '&') assert joinWithAmpersand('a', 'b') == 'a&b'
  • 36.
    Classic Operations on FunctionalData Types list filter map fold
  • 37.
    Classic Operations on FunctionalData Types list findAll collect inject
  • 38.
    Classic Operations on FunctionalData Types list any every sort min sum
  • 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 // Proceduralstyle 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 doesas 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')
  • 50.
    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')
  • 51.
    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(' ')
  • 52.
    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()
  • 53.
    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
  • 54.
    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
  • 55.
    Tail-Recursive factorial(6, 1) factorial(5, 6) factorial(4, 30) factorial(3, 120) factorial(2, 360) factorial(1, 720)
  • 56.
    Tail-Recursive factorial(6, 1) factorial(5, 6) factorial(4, 30) factorial(3, 120) factorial(2, 360) factorial(1, 720)
  • 57.
    Tail Call Optimization 3techniques: 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 3techniques: 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 deffactorial 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 deffactorial 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 deffactorial 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
  • 65.
    Memoization def fibonacci fibonacci ={ n -> n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2) } fibonacci(35) // 9.935 seconds
  • 66.
    Memoization def fibonacci fibonacci ={ n -> n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2) }.memoize() fibonacci(35) // 0.002 seconds
  • 67.
    Memoization 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)
  • 68.
    “Functional” is morea way of thinking than a tool set Neal Ford
  • 69.
  • 71.