# Functional programming

Senior Software Engineer at Equal Experts em Equal Experts
26 de Oct de 2016
1 de 74

### Functional programming

• 1. Functional Programming Elsevier 2016-07-26 Prashant Kalkar VTW Senior software developer
• 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.
• 4. Shortcomings of current model (with examples)
• 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
• 12. Mathematical Expression Always results into a value. In FP, entire program can be presented as a Mathematical expression Well almost.
• 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.
• 22. Side effects
• 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.
• 26. Eliminating or delaying the side effects
• 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.
• 29. Managing IO Printing a canvas def draw() = { println("-" * (width + 2)) rows.foreach { row => println("|" + row.map(p => p.toString).mkString + "|") } println("-" * (width + 2)) }
• 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.
• 37. Structure of Functional Code base Pure Functional Core Thin outer layer of side effects
• 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 }
• 42. Using option def get(lookupKey: Int): Option[String] = { for(entry <- entrySet) { if(entry.key == lookupKey) { Some(entry.value) } } None } Lambda signature : Int => Option[String]
• 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)
• 47. Higher order functions
• 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
• 54. Declarative Style
• 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.
• 57. More benefits of Purity
• 58. Preserves the previous version of itself. Persistent Data structures
• 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)
• 62. OO vs FP
• 63. FP Type hierarchy Sub Type1 Sub Type2 Functions Functions Functions Functions Every function will switch on subtypes and provide functionality Type
• 64. OO type hierarchy Data Functions Data Functions Data Functions Data Functions Data Functions Switch cases are avoided with polymorphic behaviour
• 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.
• 71. More ADT Examples Option Some | None Either Left | Right Try Success | Failure Future Success | Failure
• 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
• 74. Thank You Questions?