Discovering Functional Treasure
in
Idiomatic Groovy
Naresha K
Enteleki Solutions
!
naresha.k@gmail.com
@naresha_k
An imperative
language on JVM
A dynamic
with Functional Flavour
The origin
http://radio-weblogs.com/0112098/2003/08/29.html
initial idea was to make a little dynamic language which
compiles directly to Java classes and provides all the nice
(alleged) productivity benefits
- James Strachan
Prerequisites
Function
!
def sayHello(){!
! println 'Hello'!
}!
!
sayHello()!
Closure
def wish = {!
! println "Hello"!
}!
!
wish()
def wishFriend = {!
! println "Hello $it"!
} !
!
wishFriend 'Raj'
def wishFriend = { to ->!
! println "Hello $to"!
}!
!
wishFriend 'Raj'
Closure - No Arg
def wish = { ->!
! println "Hello"!
}!
!
wish()
Closure - Multiple args
def wishWithMessage = { to, message ->!
! println "Hello $to, $message"!
}!
!
wishWithMessage "Raj", "Good Evening"
Closures = Power functions
def wishWithMessage = { to, message ->!
! println "Hello $to, $message"!
}!
!
wishWithMessage "Raj", "Good Evening"
def <var> = <closure>
Functions as Values
Sample Data
import groovy.transform.ToString!
!
@ToString(includeNames=true)!
class Geek{!
String name!
int age!
List<String> languages!
}
def geeks = []!
geeks << new Geek(name: 'Raj', age: 24, !
! languages: ['Java', 'Groovy'])!
geeks << new Geek(name: 'Arun', age: 35, !
! languages: ['Java', 'Scala', 'Clojure'])!
geeks << new Geek(name: 'Kumar', age: 28, !
! languages: ['Groovy', 'Scala'])!
Geeks who can speak Groovy
def findGroovyGeeksImperative(geeks){!
! def groovyGeeks = []!
! for(geek in geeks){!
! if(geek.languages.contains('Groovy')){!
! groovyGeeks << geek!
! }!
! }!
! groovyGeeks!
}
Geeks who can speak Groovy
def findGroovyGeeksImperative(geeks){!
! def groovyGeeks = []!
! for(geek in geeks){!
! if(geek.languages.contains('Groovy')){!
! groovyGeeks << geek!
! }!
! }!
! groovyGeeks!
}
Generalised
def findGeeks(geeks, String language){!
! def knowsLang = []!
! for(geek in geeks){!
! if(geek.languages.contains(language)){!
! knowsLang << geek!
! }!
! }!
! knowsLang!
}!
Towards Idiomatic Groovy
def findGroovyGeeksFunctional(geeks){!
! geeks.findAll({it.languages.contains('Groovy')})!
}
def findGroovyGeeksFunctional(geeks){!
! geeks.findAll() {it.languages.contains('Groovy')}!
}
def findGroovyGeeksFunctional(geeks){!
! geeks.findAll {it.languages.contains('Groovy')}!
}
Reusable
def knowsGroovy = { geek -> !
! geek.languages.contains('Groovy')!
}!
!
def findGeeksFunctional(geeks, criterion){!
! geeks.findAll(criterion)!
}!
!
println findGeeksFunctional(geeks, knowsGroovy)
Higher Order Functions
Strategy Pattern
def knowsGroovy = { geek -> !
! geek.languages.contains('Groovy')!
}!
!
def knowsClojure = { geek ->!
! geek.languages.contains('Clojure')!
}!
!
def findGeeksFunctional(geeks, criterion){!
! geeks.findAll(criterion)!
}!
!
println findGeeksFunctional(geeks, knowsGroovy)!
println findGeeksFunctional(geeks, knowsClojure)
Command Pattern
def sayHello = {!
! println "Hello"!
}!
!
def sayHi = {!
! println "Hi"!
}!
!
[sayHello, sayHi].each{ command ->!
! command()!
}
Execute Around
def sayHello = {!
! println "Hello"!
}!
!
def sayHi = {!
! println "Hi"!
}!
!
[sayHello, sayHi].each{ command ->!
! println "Before Command"!
! command()!
! println "After Command"!
}
Code Smell!
def knowsGroovy = { geek -> !
! geek.languages.contains('Groovy')!
}!
!
def knowsClojure = { geek ->!
! geek.languages.contains('Clojure')!
}
After DRYing
def knowsLanguage = { geek, language ->!
! geek.languages.contains(language)!
}!
!
def findGeeks(geeks, criterion, String language){!
! geeks.findAll {criterion(it, language)}!
}!
!
println findGeeks(geeks, knowsLanguage, 'Groovy')
A Better Approach
def knowsLanguage = { geek, language ->!
! geek.languages.contains(language)!
}!
!
def knowsGroovy = knowsLanguage.rcurry('Groovy')!
def knowsClojure = knowsLanguage.rcurry('Clojure')!
!
def findGeeks(geeks, criterion){!
! geeks.findAll(criterion)!
}!
!
println findGeeks(geeks, knowsGroovy)!
println findGeeks(geeks, knowsClojure)
Curried Functions
Geeks
• Knows Groovy
• At least 25 years old
Composing em
def atleast25YearsOld = { geek ->!
! geek.age >= 25!
}!
!
def findGeeks(geeks, criterion){!
! geeks.findAll(criterion)!
}!
!
def findGroovyGeeks = (this.&findGeeks)!
! .rcurry(knowsGroovy)!
def findGeeksAtLeast25 = (this.&findGeeks)!
! .rcurry(atleast25YearsOld)
def findGroovyGeeksOlderThan24 = !
! findGeeksAtLeast25 << findGroovyGeeks!
!
println findGroovyGeeksOlderThan24(geeks)
Function Composition
Towards Immutable Data
def geeks = []!
geeks << new Geek(name: 'Raj', age: 24, !
! languages: ['Java', 'Groovy'])!
geeks << new Geek(name: 'Arun', age: 35, !
! languages: ['Java', 'Scala', 'Clojure'])!
geeks << new Geek(name: 'Kumar', age: 28, !
! languages: ['Groovy', 'Scala'])
geeks2 = geeks + new Geek(name: 'Mark', age: 40, !
! languages: ['Lisp', 'Haskell'])
geeksImmutable = geeks.asImmutable()
Towards Immutable Data …
def geeksOrderedByAge = geeks.sort { it.age }!
println geeksOrderedByAge!
println geeks
def geeksOrderedByAge = geeks.sort false, { it.age }!
println geeksOrderedByAge!
println geeks
Pure Functions
Demo
import groovy.transform.*!
!
@TailRecursive!
def factorial(number, fact = 1){!
! number == 0 ? fact : factorial(number - 1, fact * number)!
}!
!
println factorial(2500G)
Tail Call Optimization
Prior to Groovy 2.3
def fact!
fact = { number, result ->!
! number == 0 ? result : !
! ! fact.trampoline(number-1, result * number)!
}.trampoline()
Slow Functions
import groovy.transform.*!
!
@Memoized!
def timeConsumingOperation(int number){!
! println "Performing computation"!
! number * number!
}!
!
println timeConsumingOperation(2)!
println timeConsumingOperation(2)
Memoization
Map Filter Reduce
println geeks.findAll { it.languages.contains('Groovy')}!
! ! ! .collect {it.age}!
! ! ! .sum()!
!
println geeks.findAll { it.languages.contains('Groovy')}!
! ! ! .collect {it.age}!
! ! ! .with{ sum()/ size()}
Defer
Defer
import groovy.transform.*!
!
class Website{!
! String address!
! @Lazy !
! URL url = address.toURL()!
}!
!
!
def fuconf = new Website(address: 'http://functionalconf.com/')!
println fuconf.dump()!
!
def content = fuconf.url.text!
println content.grep("n").size()!
println fuconf.dump()!
Lazy Evaluation
Recursion vs Iteration
Recursion vs Iteration
def ages = geeks.collect { it.age }!
!
def sum!
sum = { head, tail ->!
! if(!tail){!
! ! head!
! }!
! else{!
! ! head + sum(tail.head(), tail.tail())!
! }!
}!
!
println(sum(0, ages))
Recursion vs Iteration
println ages.inject(0) { s, item ->!
! s + item!
}
The Ultimate Lesson
https://twitter.com/mfeathers/status/29581296216
Functional Treasures
Functions as values (First class citizens)
Higher order functions
Curried Functions
Function Composition
Pure Functions (Immutability)
Tail Call Optimization
Memoization
Lazy Evaluation
Welcome to
References
• https://github.com/naresha/functionalconf2014

Discovering functional treasure in idiomatic Groovy

  • 1.
    Discovering Functional Treasure in IdiomaticGroovy Naresha K Enteleki Solutions ! naresha.k@gmail.com @naresha_k
  • 3.
    An imperative language onJVM A dynamic with Functional Flavour
  • 4.
    The origin http://radio-weblogs.com/0112098/2003/08/29.html initial ideawas to make a little dynamic language which compiles directly to Java classes and provides all the nice (alleged) productivity benefits - James Strachan
  • 5.
  • 6.
    Function ! def sayHello(){! ! println'Hello'! }! ! sayHello()!
  • 8.
    Closure def wish ={! ! println "Hello"! }! ! wish() def wishFriend = {! ! println "Hello $it"! } ! ! wishFriend 'Raj' def wishFriend = { to ->! ! println "Hello $to"! }! ! wishFriend 'Raj'
  • 9.
    Closure - NoArg def wish = { ->! ! println "Hello"! }! ! wish()
  • 10.
    Closure - Multipleargs def wishWithMessage = { to, message ->! ! println "Hello $to, $message"! }! ! wishWithMessage "Raj", "Good Evening"
  • 11.
    Closures = Powerfunctions def wishWithMessage = { to, message ->! ! println "Hello $to, $message"! }! ! wishWithMessage "Raj", "Good Evening" def <var> = <closure> Functions as Values
  • 12.
    Sample Data import groovy.transform.ToString! ! @ToString(includeNames=true)! classGeek{! String name! int age! List<String> languages! } def geeks = []! geeks << new Geek(name: 'Raj', age: 24, ! ! languages: ['Java', 'Groovy'])! geeks << new Geek(name: 'Arun', age: 35, ! ! languages: ['Java', 'Scala', 'Clojure'])! geeks << new Geek(name: 'Kumar', age: 28, ! ! languages: ['Groovy', 'Scala'])!
  • 13.
    Geeks who canspeak Groovy def findGroovyGeeksImperative(geeks){! ! def groovyGeeks = []! ! for(geek in geeks){! ! if(geek.languages.contains('Groovy')){! ! groovyGeeks << geek! ! }! ! }! ! groovyGeeks! }
  • 14.
    Geeks who canspeak Groovy def findGroovyGeeksImperative(geeks){! ! def groovyGeeks = []! ! for(geek in geeks){! ! if(geek.languages.contains('Groovy')){! ! groovyGeeks << geek! ! }! ! }! ! groovyGeeks! }
  • 15.
    Generalised def findGeeks(geeks, Stringlanguage){! ! def knowsLang = []! ! for(geek in geeks){! ! if(geek.languages.contains(language)){! ! knowsLang << geek! ! }! ! }! ! knowsLang! }!
  • 16.
    Towards Idiomatic Groovy deffindGroovyGeeksFunctional(geeks){! ! geeks.findAll({it.languages.contains('Groovy')})! } def findGroovyGeeksFunctional(geeks){! ! geeks.findAll() {it.languages.contains('Groovy')}! } def findGroovyGeeksFunctional(geeks){! ! geeks.findAll {it.languages.contains('Groovy')}! }
  • 17.
    Reusable def knowsGroovy ={ geek -> ! ! geek.languages.contains('Groovy')! }! ! def findGeeksFunctional(geeks, criterion){! ! geeks.findAll(criterion)! }! ! println findGeeksFunctional(geeks, knowsGroovy) Higher Order Functions
  • 18.
    Strategy Pattern def knowsGroovy= { geek -> ! ! geek.languages.contains('Groovy')! }! ! def knowsClojure = { geek ->! ! geek.languages.contains('Clojure')! }! ! def findGeeksFunctional(geeks, criterion){! ! geeks.findAll(criterion)! }! ! println findGeeksFunctional(geeks, knowsGroovy)! println findGeeksFunctional(geeks, knowsClojure)
  • 19.
    Command Pattern def sayHello= {! ! println "Hello"! }! ! def sayHi = {! ! println "Hi"! }! ! [sayHello, sayHi].each{ command ->! ! command()! }
  • 20.
    Execute Around def sayHello= {! ! println "Hello"! }! ! def sayHi = {! ! println "Hi"! }! ! [sayHello, sayHi].each{ command ->! ! println "Before Command"! ! command()! ! println "After Command"! }
  • 21.
    Code Smell! def knowsGroovy= { geek -> ! ! geek.languages.contains('Groovy')! }! ! def knowsClojure = { geek ->! ! geek.languages.contains('Clojure')! }
  • 22.
    After DRYing def knowsLanguage= { geek, language ->! ! geek.languages.contains(language)! }! ! def findGeeks(geeks, criterion, String language){! ! geeks.findAll {criterion(it, language)}! }! ! println findGeeks(geeks, knowsLanguage, 'Groovy')
  • 23.
    A Better Approach defknowsLanguage = { geek, language ->! ! geek.languages.contains(language)! }! ! def knowsGroovy = knowsLanguage.rcurry('Groovy')! def knowsClojure = knowsLanguage.rcurry('Clojure')! ! def findGeeks(geeks, criterion){! ! geeks.findAll(criterion)! }! ! println findGeeks(geeks, knowsGroovy)! println findGeeks(geeks, knowsClojure) Curried Functions
  • 24.
    Geeks • Knows Groovy •At least 25 years old
  • 25.
    Composing em def atleast25YearsOld= { geek ->! ! geek.age >= 25! }! ! def findGeeks(geeks, criterion){! ! geeks.findAll(criterion)! }! ! def findGroovyGeeks = (this.&findGeeks)! ! .rcurry(knowsGroovy)! def findGeeksAtLeast25 = (this.&findGeeks)! ! .rcurry(atleast25YearsOld) def findGroovyGeeksOlderThan24 = ! ! findGeeksAtLeast25 << findGroovyGeeks! ! println findGroovyGeeksOlderThan24(geeks) Function Composition
  • 26.
    Towards Immutable Data defgeeks = []! geeks << new Geek(name: 'Raj', age: 24, ! ! languages: ['Java', 'Groovy'])! geeks << new Geek(name: 'Arun', age: 35, ! ! languages: ['Java', 'Scala', 'Clojure'])! geeks << new Geek(name: 'Kumar', age: 28, ! ! languages: ['Groovy', 'Scala']) geeks2 = geeks + new Geek(name: 'Mark', age: 40, ! ! languages: ['Lisp', 'Haskell']) geeksImmutable = geeks.asImmutable()
  • 27.
    Towards Immutable Data… def geeksOrderedByAge = geeks.sort { it.age }! println geeksOrderedByAge! println geeks def geeksOrderedByAge = geeks.sort false, { it.age }! println geeksOrderedByAge! println geeks Pure Functions
  • 28.
  • 29.
    import groovy.transform.*! ! @TailRecursive! def factorial(number,fact = 1){! ! number == 0 ? fact : factorial(number - 1, fact * number)! }! ! println factorial(2500G) Tail Call Optimization
  • 30.
    Prior to Groovy2.3 def fact! fact = { number, result ->! ! number == 0 ? result : ! ! ! fact.trampoline(number-1, result * number)! }.trampoline()
  • 31.
  • 32.
    import groovy.transform.*! ! @Memoized! def timeConsumingOperation(intnumber){! ! println "Performing computation"! ! number * number! }! ! println timeConsumingOperation(2)! println timeConsumingOperation(2) Memoization
  • 33.
    Map Filter Reduce printlngeeks.findAll { it.languages.contains('Groovy')}! ! ! ! .collect {it.age}! ! ! ! .sum()! ! println geeks.findAll { it.languages.contains('Groovy')}! ! ! ! .collect {it.age}! ! ! ! .with{ sum()/ size()}
  • 34.
  • 35.
    Defer import groovy.transform.*! ! class Website{! !String address! ! @Lazy ! ! URL url = address.toURL()! }! ! ! def fuconf = new Website(address: 'http://functionalconf.com/')! println fuconf.dump()! ! def content = fuconf.url.text! println content.grep("n").size()! println fuconf.dump()! Lazy Evaluation
  • 36.
  • 37.
    Recursion vs Iteration defages = geeks.collect { it.age }! ! def sum! sum = { head, tail ->! ! if(!tail){! ! ! head! ! }! ! else{! ! ! head + sum(tail.head(), tail.tail())! ! }! }! ! println(sum(0, ages))
  • 38.
    Recursion vs Iteration printlnages.inject(0) { s, item ->! ! s + item! }
  • 39.
  • 40.
    Functional Treasures Functions asvalues (First class citizens) Higher order functions Curried Functions Function Composition Pure Functions (Immutability) Tail Call Optimization Memoization Lazy Evaluation
  • 41.
  • 42.