discovering functional treasure in idiomatic groovy
DESCRIPTION
Slides of my talk at Functional Conf 2014 - Oct 9-11, BangaloreTRANSCRIPT
Discovering Functional Treasure in
Idiomatic Groovy
Naresha K Enteleki Solutions
@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()!
Closuredef 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 Dataimport 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 Groovydef 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 Arounddef 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 Approachdef 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 emdef 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 Datadef 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
Deferimport 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