2. Shortcoming of current model.
What is Functional Programming (FP).
Benefits of Functional Programming.
Explore side effects and eliminate them.
Higher order functions and lambda expressions.
Collection processing in FP.
Declarative Programming.
Some more benefits in FP.
FP vs OO and when to use them.
Agenda
3. Takeaways
What is FP.
Why to use FP.
Initiate the thinking in FP.
Techniques to eliminate side effects.
Structure of functional code base.
Structural difference between FP and OO
When to use FP solution.
When to use OO solution.
5. Consider Example
int f() {
y = y + 1; return y;
}
What is the output of function call
f()?
Requires or depends on
external state.
Need to know the history of
interactions with the
system.
Multiple calls will cause side
effects.
y = 1 // initial value
Now what will be the output?
6. State of Ellipse
Ellipse ellipse = new Ellipse(new Rectangle(0, 0, 100, 100));
BoundingBox boundingBox = ellipse.boundingBox;
boundingBox.inflate(10, 10);
Mutation in object graph affect many graph objects
Tracking change is difficult.
7. boolean hitMonster = monster.hitByShooting(gunShot);
boolean hitPlayer = player.hitByShooting(gunShot);
Are these statements independent of each other?
Mutation impose order.
8. Solutions?
Clause of issue was mutation in the programs. More
precisely global mutation.
Let put in some constrains on functions
Only depend on the input parameters.
For same input, same output.
No mutation of state.
9. int f(int y) { return y + 1; }
Now what will be the output of functional call f(5)?
Ellipse ellipse = new Ellipse(new Rectangle(0, 0, 100, 100));
BoundingBox boundingBox = ellipse.boundingBox;
boundingBox.inflate(10, 10);
If everything is immutable, what will be state of Ellipse after
these operations?
10. We defined a true Mathematical expression
Only depend on the input parameters.
For same input, same output.
No mutation of state or Side effects
Also called as Pure functions.
Functional programming is programming with Pure functions.
11. Functional Programming
In computer science, functional programming is a
programming paradigm—a style of building the structure
and elements of computer programs—that treats
computation as the evaluation of mathematical
functions and avoids changing-state and mutable data.
- Wikipedia
13. int x = 2 * 3 + 5;
Is it an expression?
= Expression
= Expression
= Statement (requires mutation)
= Statement (requires mutation)
= Expression
= Expression (mutation is avoided)
int x = a < 5 ? 5 : a;
if(a < 5) { x = 5; } else { x = a; }
val x = if(a < 5) 5 else a (scala)
for(int i=0; i<a.length;i++) b[i]= a[i] * 2;
val b = for(i <- a) yield i * 2 (scala)
14. Benefits of Pure functions
Context Independent
Depends only on parameters.
Highly reusable
Because they are context independent.
Can be reasoned Locally
Everything is in the function body.
Highly testable.
15. Can be reasoned Locally?
It's ok, for small functions
What about functions that are calling other
complex functions.
Referential Transparency & Substitution Model
16. Referential Transparency
If a program can be updated by replacing the function call
with result of the function call without changing its
behaviour then that function is Referentially Transparent
Function.
17. Example
int square(int a) { return a * a; }
int x = square(4); => x = 16;
Replacing square(4) with 16 will not change the behaviour
of the program.
So function square is Referentially transparent
18. Substitution Model
Substitution model is a model of reasoning a program by
substituting the referentially transparent functions with their
values.
This makes the understanding of complex program easy.
This enables the local reasoning of the functions.
And a good name for a function helps.
19. View of Function in FP world
What do you think when you think of a Function in
conventional languages?
We think in terms of what that function does.
In FP you think only about input and output types.
def f1: String => Int = ???
def f2: Int => Boolean = ???
def f3[A, B]: A => B = ???
20. Benefits of FP View
Functions become more composable.
System become more modular.
Higher order functions are possible.
They just care whether function arguments follow a
type signature.
23. boolean hitMonster = monster.hitByShooting(gunShot);
// reduce the power of the
gunshot
boolean hitPlayer = player.hitByShooting(gunShot);
What is a side effect?
24. Code inside monster class
boolean hitByShooting(Gunshot gunShot) {
if(isInRange()) {
gunShot.reducePower();
return true;
}
return false;
}
<= Happening on the side
Not visible through return type.
Not Referentially Transparent function (can not be substituted)
25. Types of Side effects
Mutation of value.
Throwing an Exception.
I/O operations like printing or File operations.
Communicating with DB.
Remote communication.
27. def hitByShooting(gunShot : Gunshot): (Boolean, Gunshot) = {
if(isInRange()) {
val usedGunshot = gunShot.reducePower()
(true, usedGunshot)
} else
(false, gunShot)
}
Referentially Transparent Function (can be substituted).
Visible in returned value
No more a side
effect
28. val (hitMonster, usedGunshot) = monster.hitByShooting(gunShot)
// reduce the power of the gunshot
val hitPlayer = player.hitByShooting(usedGunshot)
Inter-dependency is now visible in the code.
Types of pure functions make the dependency visible.
30. Traditional Testing with DI
def draw(ds: DisplayService) = {
ds.println("-" * (width + 2))
rows.foreach { row =>
ds.println("|" + row.map(p => p.toString).mkString + "|")
}
ds.println("-" * (width + 2))
}
Injected & mocked during tests.
31. Functional Implementation
def canvasAsString(): String = {
val topBorder = "-" * (width + 2) + "n"
val canvasRows = rowAsString()
val bottomBorder = "-" * (width + 2)
topBorder + canvasRows + bottomBorder
}
println(canvasAsString()) // only line in program with side effect.
32. Another example
Buying coffee
class Cafe {
def buyCoffee(cc: CreditCard): Coffee = {
val cup = new Coffee()
cc.charge(cup.price)
cup
}
}
Clearly a side effect.
33. Delaying the side effect
class Cafe {
def buyCoffe(cc: CreditCard): (Coffe, Charge) = {
val cup = new Coffee()
(cup, Charge(cc, cup.price))
// charge details are returned instead of processed
}
}
Here we’ve separated the concern of creating a charge from the
processing or interpretation of that charge.
34. Buying multiple coffees
Buy multiple cups of coffee with a single charge on credit
card.
def buyCoffees(cc: CreditCard, n: Int) : (List[Coffee], Charge)
// call buyCoffee n times
// combine n changes into a single charge on credit card.
This was possible since Charge is now a value, and can be
processed further.
35. Separation of Concerns
Impure function f() of type A => B can be split into
A pure function of type A => D
where D is some description of the result of f().
Description of steps or required info to execute the
steps
An impure function of type D => B
which ack as an interpreter of these descriptions.
Executor of the steps.
36. Benefits of this Separation
Pure
Impure
Function
Input Output PureInput Output
Value always allow further processing
Input No or incomplete output
Denote the end of the processing chain.
Or Miss intermediate values.
38. Exception
def failingFn(i : Int) : Int = {
val y: Int = throw new Exception("fail!");
try {
val x = 42 + 5
x + y
} catch { case e : Exception => 43 }
}
This program will fail at runtime.
39. Not Referentially Transparent
def failingFn(i : Int) : Int = {
// replace y with its value
try {
val x = 42 + 5
x + ((throw new Exception("fail!")): Int)
} catch { case e : Exception => 43 }
}
This program will not fail.
Exception can not be reasoned locally.
40. More issues with Exceptions
Exceptions are not type safe.
def map(f: String => Int) = ???
Map can not know f() can throw an exception.
This does not fit with our view of function in FP world.
41. Option data type
Two possible values
Some - holds some value.
None - No value exists.
def get(lookupKey: Int): String = {
for(entry <- entrySet) {
if(entry.key == lookupKey) { return entry.value }
}
throw new ValueNotFoundException() // or return null
}
43. Other data type to handle Errors
Optional (Java 8)
Try (scala)
Either (scala)
Maybe (Haskell)
44. Internal Side effects
Consider adding element of an array
int sum(int[] intList) {
int sum = 0;
for(int i : intList) {
sum = sum + i; // mutation
}
return sum;
}
This is acceptable inside a pure function.
45. Eliminating Internal Side effect
Use recursion
def sum(intList: List[Int]) : Int = {
if(intList.isEmpty) 0
else intList.head + sum(intList.tail)
}
Recursion is declarative.
46. Tail Recursion
Stackoverflow? Use tail recursion.
def sum(intList: List[Int], total: Int): Int = {
intList match {
case Nil => total
case head :: tail => sum(tail, total + head)
// same stack frame
}
}
sum(List(1, 2, 3), 0)
48. Consider two functions
def doubleIt(a : List[Int]) : List[Int] = {
a match { case Nil => Nil
case head :: tail => (head * 2) :: doubleIt(tail)
}}
def squareIt(a : List[Int]) : List[Int] = {
a match { case Nil => Nil
case head :: tail => (head * head) :: squareIt(tail)
}}
Example
49. Higher Abstraction
def processIt(a: List[Int], f: Int => Int) : List[Int] = {
a match { case Nil => Nil
case head :: tail => f(head) :: processIt(tail, f)
}}
Provides more opportunities of abstraction.
More opportunities for reuse.
50. Polymorphism in Types
We can do even better.
def processIt [A, B] (a: List[A], f : A => B) : List[B] = {
a match { case Nil => Nil
case head :: tail => f(head) :: processIt(tail, f)
}}
51. Map function
def map [A, B] (a: List[A], f : A => B) : List[B] = {
a match { case Nil => Nil
case head :: tail => f(head) :: map(tail, f)
}}
Part of standard library for FP languages.
52. More HOFs
Filter elements of the list.
def filter[A](x: List[A], f: A => Boolean): List[A] = ???
Similar to map but function argument result into a list
def flatMap[A, B](x: List[A], f: A => List[B]) : List[B] = ???
Fold a list into single value.
def foldLeft[A, B](x: List[A], f: (B, A) => B): B = ???
53. Function as First class citizen
First class Functions.
Pass function as arguments.
Return function as return types.
Declare function locally (like local variable).
Assign function to a variable.
Define function literals (also called as lambdas)
(x: Int) => x + 1
55. Imperative Style
Write program to find even numbers, double them and sum them up.
public int calculate() {
int[] numbers = {1, 5, 10, 9, 12};
List<Integer> doubledEvenNumbers = new ArrayList<>();
for (int number : numbers)
if(number % 2 == 0)
doubledEvenNumbers.add(number * 2);
int total = 0;
for(int even : doubledEvenNumbers)
total = total + even;
return total;
}
56. Declarative Style
Same code in declarative style with Higher order functions
List(1, 5, 10, 9, 12)
.filter(e => e % 2 == 0)
.map(e => e * 2)
.sum
Focus on what, instead of when.
Class schedule example.
59. Memoization
Result of expensive pure function can be cached and
reused again, this technique is called as Memoization.
// example of memoization in Groovy
def static sumFactors = { number ->
factorsOf(number).inject(0, {i, j -> i + j})
}
def static sumOfFactors = sumFactors.memoize()
60. Lazy evaluation
Expressions are not evaluated unless needed.
Haskell everything is evaluated lazily (as needed)
In Java and Scala streams are Lazy collections
Infinite collections can be created with lazy collection.
def fib(a: Int, b: Int): Stream[Int] =
a #:: fib(b, a + b)
val firstSeven = fib(1, 1).take(7)
firstSeven.toList // => List(1, 1, 2, 3, 5, 8, 13)
61. Parallel Programming
Independent pure functions can be executed in parallel.
Pure functions are inherently thread safe.
Declarative programming with language constructs help
parallel programming further.
val people : Array[People]
val (minors, adults) = people.par.partition(p => p.age > 18)
65. Switch cases good or bad
OO programmers consider Switch cases bad.
Conditional code. Hard to maintain
Hunt down existing cases to add new cases.
Program will misbehave if you miss to update one.
FP enhanced switch cases to pattern matching and
promote them.
What’s the Truth??
66. Types and Operations
Type against OperationsType
Operations
Infinite Type,
Finite Operations
Finite Type,
Infinite Operations
OO Solution better
FP Solution better
67. Infinite Type, Finite Operations
Implemented as Polymorphism (class hierarchy).
Easy to add new Type by Subtyping.
Difficult to add new Operations.
Finite Type, Infinite Operations
FP structures called Algebraic data types (ADTs)
Easy to add new operations using Pattern matching.
Difficult to add new Types.
68. Algebraic data type
Composite Type
Defined by one more more data constructors.
data List a = Nil | Cons x (List xs)
This is defining a list data type with 2 constructors.
69. ADT Example
Representation in scala.
interface List
class Nil extends List
class Cons(head, tail) extends List
In OO terms these constructors can be mapped to
subtypes.
70. ADT Example
ADTs have finite number of constructors or subtypes.
For list we have two constructors or subtypes
Empty list
Non empty list with head and tail.
These constructions are exhaustive and no new
subtype will ever be added.
72. OO & FP
Use ADTs and pattern matching for finite types
Use OO polymorphism for infinite types and
limited functions on those types.
Pure functions can be used in OO structure as
well.
73. Conclusion
Embrace immutability and Pure functions
Eliminate or delay the side effects
FP is not all or nothing
Choice is not OO or FP
FP can co-exists (nicely) with OO code
Functional in the small and OO in the large