Overview of Groovy language features and lead to Functional Programming in Groovy. As this is a code along session backed by this presentation, code examples are not include here as I code them live.
2. Hello World…
Concise - convey more in less
Groovy Classes Are Java Classes
Everything is an object.
3. Flexibility
Dynamic Language
Program extension at runtime by altering
behaviour and structure of objects/types -
“Open Classes”
Code synthesis
Add code at runtime
As opposed to manipulating byte code in Static
Languages like Java.
Groovy’s extension to JDK is called GDK
4. Uncertainty
Pimp(extend) my Library
Extending the class - additive operation.
Monkey Patching
Redefining things again - Don’t cause
fires!
Break glass in case of fire - reach out
for this meta-programming hammer only
then.
Adding != Redefining
5. Typing 101
Type?
How do I interpret data in that block of
memory and what operations can I perform
on it?
Safety?
Any attempt to misinterpret data is
caught at compile time OR generates a
well-specified error at runtime.
6. Dynamic & Static Typing
Statically Typed Language
Type of every expression can be
determined by static program analysis
(before it runs).
Compile-time type checking.
Align types naturally or by casting.
Dynamically Typed
Runtime type checking, not compile-time
checking
7. Dynamic Typing
Infers Type based on context.
Numbers and Decimals
We rely on static typing for code
safety, but it still can fail us.
Generics
ArrayStoreException
Dynamic & Static Typing
8. Optional & Duck Typing
Optionally Typed
Specify when you need, and leave it to Groovy
to figure it out where you don’t care.
Duck Typing
Don’t need a type to invoke a method on
object. If its there, you can invoke it
Objects behave as demanded of them in a given
context, rather than ensuring that they are
of a specific type.
You can treat it like a dog or even god.
9. Ram (Static) Balram (Dynamic)
Static typing catches bugs with
the compiler and keeps you out
of trouble.
Static typing only catches some bugs,
and you can’t trust the compiler to
do your testing
Static languages are easier to
read because they’re more
explicit about what the code
does.
Dynamic languages are easier to read
because you write less code.
At least I know that the code
compiles.
Just because the code compiles
doesn’t mean it runs.
I trust the static typing to
make sure my team writes good
code.
The compiler doesn’t stop you from
writing bad code.
Debugging an unknown object is
impossible.
Debugging overly complex object
hierarchies is unbearable.
Compiler bugs happen at
midmorning in my office; runtime
bugs happen at midnight for my
customers.
There’s no replacement for testing,
and unit tests find more issues than
the compiler ever could.
Source: http://www.smashingmagazine.com/2013/04/18/introduction-to-programming-type-systems/
10. Use correct typing to solve the problem
at hand
Doesn’t matter if you have to use more than
one at a given time.
Use Static typing where possible,
Dynamic typing when needed - Eric Meijer
Avoid Polarization
Like a good manager, play on strengths of
each :-)
Dynamic or Static
Typing?
11. Weakly Typed
The type of a value depends on how it
is used.
Can mix operations between mismatched
types without explicit conversion
C lets you define object types as
structures, but it doesn’t do much to
enforce or remember them. C automatically
converts between many types.
http://c2.com/cgi/wiki?WeakAndStrongTyping
12. Strongly Typed Language
A Language in which all expressions are type
consistent is Strongly Typed.
A value has a type and that type cannot change.
What you can do to a value depends on the type of
the value.
Can’t mix operations between mismatched types, if
you do, the runtime catches you. You must convert
explicitly.
But, being liberal with type conversions doesn't
really mean the same as saying that the language
is weakly typed!
http://c2.com/cgi/wiki?WeakAndStrongTyping
14. Strings in Groovy
Literal Strings
String Interpolation
GString - Nah, stop fantasising!
Allow in-place evaluation of expressions…and
for that you have to use the $ ;-)
Multi-line String
Lazy Evaluation
Slashy Strings
15. Match found!
Pattern from String
use operator ~
Determine a match =~
For exact match ==~
Regex Readability
Prefer Slashy strings over normal ones
16. Truth & Difficult
Situations
Groovy Truth
Dealing with Exceptions
No force to catch Java’s Checked Exceptions
Dealing with Absent Values
null is an object, try null.getClass() and
toString()
null-safe operator (?.)
17. Methods and
Parameters
Properties (Getters and Setters)
Trailing Optional Parameters
Positional parameters -
@TupleConstructor
@Cannonical = @EqualsAndHashCode,
@ToString and @TupleConstructor
18. Hello Operator!
Preserve semantics when using Operator
overloading.
When someone asks you - what does this
operator do? You know the answer :-)
Use it to harmonise with the domain/
context
Typically very nice for value objects.
19. Multi-Methods
Also called as Multiple Dispatch.
Dispatch based on type of the receiver.
Example, Java/C#/Scala
and based on Run-time Type of the arguments,
not on Compile-time type (as in Java).
Example, Clojure
The equals method.
20. A Peek at GDK
Producing and Consuming XML
Spawning Processes
Using Files
Using Configuration
Avoid properties hell.
Using SQL
22. Traits
Like interfaces, they cannot be
instantiated.
However, unlike interfaces, they can have
implementation and state.
Can contain abstract methods.
Classes can inherit multiple traits or
implement at run-time.
Trait can extend another trait and
implement interfaces as well.
Methods are “real” and visible from Java as
well.
23. Closures
Objects = data + behavior, whereas
Closures = behavior.
Convert Methods to Closures.
Using the object.&method
Static, Instance or in Script methods.
The with closure.
24. Closures in Action
AOP around style using Closures.
Loan My Resource - like try-with-
resources in Java.
Closure as an interface.
Query Closure to find
maximumNumberOfParameters
parameterTypes
and alter the response accordingly -
polymorphic closures.
25. More on Closures
Memoized Closures and @Memoized
methods
Remember me and I’ll help you!
Trampolined Closures and
@TailRecursive methods
Don’t jump that high, but instead jump
many times!
26. More on Closures
Curried Closures
Spice that helps us re-shape and re-
purpose!
curry, rcurry
Composing Closures
The way to tackle complexity!
>> and <<
27. When to use Closures?
Eliminating Dependencies
Esp. on single method interfaces
Building Declarative APIs and
Preserving encapsulation.
External iterators or getters expose innards
of an aggregate, instead use internal
iterators and call closure.
Building DSLs
Using HOFs it becomes easy to construct DSLs
Lazy Evaluation
28. Hello Collections…
Lists - [] , Maps - [:], Ranges -
(a..b), Sets
Tuples
Iterate each element.
each, reverseEach, eachWithIndex
Transform each element.
collect, collectNested, collectMany and
collectEntries
29. Collection Operations
Retrieve Elements that satisfy certain
criterion
find, findAll
Transform using predicate (collect +
~find)
findResult(s)
Combine adjacent elements
sum, inject, min, max
31. Collection Ops
Set-Style Operations
intersect, disjoint, plus (union)
Distinct and Order By
unique, sort
Partition Collection to Map
groupBy, groupEntriesBy, countBy
split and join
drop, take, dropWhile, takeWhile
32. Spreading Collections
The Spread-dot Operator (*.)
Array and List as varargs
The Spread Operator (*)
Map as an Interface.
Very useful when Stubbing
33. FP in Groovy
Like in other languages Java/C#/Ruby
etc…, in-place mutation is a standing
invitation in Groovy too!
Its hard to avoid falling into that trap.
One has to work hard to bring immutability.
A change to Immutable Object produces
another Immutable Object
Promotes caching of objects - Flyweights.
Promotes concurrent operations.
34. FP in Groovy
Use Expressions wherever possible
Statements effect change by mutation thus
encourage mutability
Expressions evaluate and return value thus
encourage immutability
Use Pure Functions wherever possible
Order of program evaluation
Referential Transparency
Memoization
37. Lazy Evaluation
Lazy Evaluation (by-name)
Don’t compute until actually required.
Lazy Evaluation (by-need)
Store the result of computation for
future use and avoid re-computation
@Lazy AST Transformation
Needs to be initialised at the time of
declaration
38. Being Lazy is efficient
Lazy defaults on Lists
Lazy Sequences
A sequence whose tail is evaluated
only someone wants to know its
value.
Materialise on demand to avoid
unnecessary computation of tail.
39. Lets implement
Immutable Stream
Stream implementation shown in these slides is available on:
https://github.com/CodeJugalbandi/FunctionalProgramming/blob/master/melodies/lazy_sequences/Stream.groovy
40. Lets implement
Immutable Stream
How can we implement
a lazy List in
Groovy?
1 ?
Stream implementation shown in these slides is available on:
https://github.com/CodeJugalbandi/FunctionalProgramming/blob/master/melodies/lazy_sequences/Stream.groovy
41. Lets implement
Immutable Stream
How can we implement
a lazy List in
Groovy?
By using closure on the
tail of the list.
Additionally for
performance - use
Memoization on that
closure.
Evaluate
tail
1 ?
1 *
2 ?
Stream implementation shown in these slides is available on:
https://github.com/CodeJugalbandi/FunctionalProgramming/blob/master/melodies/lazy_sequences/Stream.groovy
42. Ingredients for Lazy
Evaluation
Groovy uses eager evaluation for
method arguments.
Args are evaluated before passing to the
method.
To delay the evaluation of method
arguments (lazy), wrap them in Closure
Invoke the closure explicitly in the method
when we need the evaluation result.
43. Ingredients for Lazy
Evaluation
Wrap in
Closure for
Lazy Calls
for storing result of
evaluation and avoiding
computation again,
performance optimisation
CAVEAT:
Only for
pure
functions!
Evaluation
on Closure
Call
45. Stream(T head, Stream<T> tail) {
_head = head
_tail = {-> tail}.memoize()
}
new Stream<Integer>(1, new Stream(null, null))
Stream(T head, Closure tail) {
_head = head
_tail = tail.memoize()
}
new Stream<Integer>(1, {-> new Stream(null, {-> null})})
Causes Immediate
Evaluation - Eager
Hence wrap the tail
stream in a Closure at
the time of Stream
Creation - Lazy
47. Define toString
String toString() {
if (isEmpty()) ‘[]’ else “[${head()}, ?]”
}
} // End of Stream class
def empty = Stream.Empty
println empty // []
def singleton = new Stream(1, {-> empty})
println singleton // [1, ?]
def couple = new Stream(2, {-> singleton})
println couple // [2, ?]
48. def leftShift(T element) {
new Stream(element, {-> this})
}
} // End of Stream class
def stream = Stream.Empty << 1 << 2
println stream // [2, ?]
println stream.head() // 2
println stream.tail() // [1, ?]
println stream.tail().tail() // []
Consing to Stream
Prepend (Cons)
49. Construct a Stream
from few elements
static def of(T ...ts) {
def stream = Stream.Empty
ts.reverseEach {
stream = stream << it
}
stream
}
} // End of Stream class
def stream = Stream.of(1, 2, 3, 4)
println stream // [1, ?]
50. Construct a Stream
from Range
static def range(Range range, Integer step) {
Stream.of(*range.step(step))
}
} // End of Stream class
println Stream.range('a'..'z')
println Stream.range(‘A'..'Z', 2)
println Stream.range(-10..5)
println Stream.range(new Date("12-Dec-2012")..new
Date("19-Jan-2013"))
51. Force entire Stream
to Evaluate
def force() {
if (isEmpty()) []
else [head()] + tail().force()
}
} // End of Stream class
def empty = Stream.Empty
println empty // []
def stream = Stream.of(1, 2, 3, 4)
println stream.force() // [1, 2, 3, 4]
52. Drop first few and
take the rest
def drop(howMany) {
if (isEmpty() || howMany <= 0) this
else tail().drop(howMany - 1)
}
} // End of Stream class
def empty = Stream.Empty
println empty.drop(2).force() // []
def stream = Stream.of(1, 2, 3, 4)
println stream.drop(2) // [3, ?]
println stream.drop(2).force() // [3, 4]
println stream.drop(9).force() // []
53. stream as a function
of its index
def call(index) {
def list = take(index + 1).force()
if (index > list.size() - 1)
throw new Exception(“Index $index out of bounds”)
list[index]
}
} // End of Stream class
def empty = Stream.Empty
println empty(2) // Exception: Index 2 out of bounds
def stream = Stream.of(1, 2, 3, 4)
println stream(0) // 1
println stream(3) // 4
println stream(4) // Exception: Index 4 out of bounds
54. Combining Adjacent
Elements Using fold
def fold(acc, Closure fn) {
if (isEmpty()) acc
else tail().fold(fn(acc, head()), fn)
}
} // End of Stream class
def stream = Stream.of(1, 2, 3, 4)
// Sum of all elements
println stream.fold(0) { acc, elem -> acc + elem } // 10
// Product of all elements
println stream.fold(1) { acc, elem -> acc * elem } // 24
55. TailRecursive fold
@TailRecursive
def fold(stream = this, acc, Closure fn) {
if (stream.isEmpty()) acc
else fold(stream.tail(), fn(acc, stream.head()), fn)
}
} // End of Stream class
def stream = Stream.of(1, 2, 3, 4)
// Sum of all elements
println stream.fold(0) { acc, elem -> acc + elem } // 10
// Product of all elements
println stream.fold(1) { acc, elem -> acc * elem } // 24
56. Express force
in terms of fold
But why do we do this?
def force() {
fold([]) { acc, elem -> acc + elem }
}
} // End of Stream class
def empty = Stream.Empty
println empty // []
def stream = empty << 1 << 2 << 3 << 4
println stream.force() // [4, 3, 2, 1]
57. force() [4, ?]
[4] + force() [3, ?]
[4] + [3] + force() [2, ?]
[4] + [3] + [2] + force() [1, ?]
[4] + [3] + [2] + [1] + force() []
[4] + [3] + [2] + ([1] + [])
[4] + [3] + ([2] + [1])
[4] + ([3] + [2, 1])
[4] + [3, 2, 1]
[4, 3, 2, 1]
fold [4, ?] []
fold [3, ?] [4]
fold [2, ?] [4, 3]
fold [1, ?] [4, 3, 2]
fold [] [4, 3, 2, 1]
[4, 3, 2, 1]
Shape of the
process becomes
iterative after
expressing it in
terms of fold.
Earlier force
implementation was a
recursive process.
Shapes of Processes
Build-up on
stack like this
causes it to
overflow
State
passed as
parameter
State
stored on
stack
69. Infinitely Lazy
Streams, unlike lists (eager), are
lazy.
Streams, unlike lists (finite), are
infinite.
They have a starting point, but no end.
// The below will not terminate
Stream.from(4).forEach { println it }
70. Finite from Infinite
Take what we are interested in and
leave the rest in ether!
def numbersFrom4 = Stream.from(4)
println numbersFrom4.take(2).force() // [4, 5]
println numbersFrom4.take(5).force() // [4, 5, 6, 7, 8]
71. Find first 6 primes using the Sieve of
Eratosthenes
Hint: Use Stream created earlier
http://world.mathigon.org/Prime_Numbers
72. Sieve
def sieve(s) {
def first = s.head()
def rest = s.tail().filter { it % first != 0 }
new Stream(first, {-> sieve(rest)})
}
def primes = sieve(Stream.from(2)).take(6)
println primes.force() // [2, 3, 5, 7, 11, 13]
73. Non-Memoized Tail
Serious Performance hit as tail is wrapped in
closure and if when called several times the
stream is recomputed each time upon call.
75. Zip two Streams
def zip(Stream<T> that) {
if (isEmpty() || that.isEmpty())
Stream.Empty
else
new Stream(new Tuple(head(), that.head()), {-> tail().zip(that.tail()) })
}
} // End of Stream class
def s1 = Stream.of(0, 1, 2)
def s2 = Stream.of(4, 5, 6)
def zipped = s1.zip(s2)
println zipped // [[0, 4], ?]
println zipped.tail() // [[1, 5], ?]
println zipped.force() // [0, 4, 1, 5, 2, 6]
println zipped.force().collate(2) // [[2, 6], [1, 5], [0,4]]
// Sum of 2 streams
println zipped.map {
def (first, second) = it
first + second
}.force() // [4, 6, 8]
76. Zip with Index
def zipWithIndex() {
zip(from(0))
}
} // End of Stream class
def stream = Stream.of('a', 'b', 'c', 'd')
stream.zipWithIndex().forEach {
print it // [a, 0][b, 1][c, 2][d, 3]
}
77. def fibonacci(s) {
def next = s.zip(s.tail()).map {
def (first, second) = it
first + second
}
def rest = s << next.head()
new Stream(s.head(), {-> fibonacci(rest)})
}
First 10 Fibonacci Nos.
https://www.haskell.org/tutorial/functions.html
def stream = Stream.from(0).take(2)
println stream.force() // [0, 1]
// Start with seed elements 0 and 1
println fibonacci(stream).take(10).force()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
78. def fibonacci(n) {
Stream.iterate([0, 1]) {
def (first, second) = it
[second, first + second]
}.map {
def (first, second) = it
first
}.take(n)
}
println fibonacci(10).force()
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
First 10 Fibonacci Nos.
Definition as suggested by Christopher Grande
83. Thank-You!
Stream implementation shown in these slides is available on:
https://github.com/CodeJugalbandi/FunctionalProgramming/blob/master/melodies/lazy_sequences/Stream.groovy
84. References
Programming Groovy
Venkat Subramaniam
Groovy In Action
Dierk König
Structure and
Interpretation of
Computer Programs
Abelson, Sussman and
Sussman
Functional Prog.
Coursera Course
Martin Odersky
Functional Prog. with
Groovy
Arturo Herrero
On Understanding
Types, Data
Abstraction &
Polymorphism
Luca Cardelli, Peter
Wegner