Successfully reported this slideshow.
Upcoming SlideShare
×

# Loop Like a Functional Programing Native

This slide is for a 30 minutes talk, during which I share my learning w/ my colleague about Functional Programing

• Full Name
Comment goes here.

Are you sure you want to Yes No

• Be the first to like this

### Loop Like a Functional Programing Native

1. 1. Loop Like a Functional Programing native Jiaming Zhang 03/22/2017
2. 2. Recap: FP Principles Treat computation as the evaluation of math functions Prefer expression over statement Prefer recursion over iteration Pure Function No side effect Same Input -> Same Output Higher-order Function Avoid Mutation
3. 3. Why talk about loop?
4. 4. Scala Tutorial List A class for immutable linked list val list = List(1, 2, 3) list.head // => 1 list.tail // => List(2, 3) 0 :: list // => List(0,1,2,3) 0 :: 1 :: Nil // => List(0,1) List(1,2) ++ List(3,4) // => List(1,2,3,4) Scala Documentation - List
5. 5. Scala Tutorial Stream Scala Documentation - Stream
6. 6. Scala Tutorial Stream A Stream is like a list except that its elements are computed lazily . // Stream is similar to list val stream = Stream(1, 2, 3) stream.head // => 1 stream.tail // => Stream(2, 3) 0 #:: stream // => Stream(0,1,2,3) 0 #:: 1 #:: Stream.Empty // => Stream(0,1) Stream(1,2) ++ Stream(3,4) // => Stream(1,2,3,4) Scala Documentation - Stream
7. 7. Scala Tutorial Stream // Stream is the lazy version of List def foo: Int = { println("I am called") 42 } val list = 0 :: foo :: Nil // => print "I am called" val stream = 0 #:: foo #:: Stream.Empty // => print nothing stream.tail // => print "I am called" // => return Stream(42) Scala Documentation - Stream
8. 8. Scala Tutorial Pattern Match Playing with Scala’s pattern matching
9. 9. Scala Tutorial Pattern Match It can match constant just as switch ... case ... in Java def getNumAsWord(n: Int): String = n match { case 1 => "One" case 2 => "Two" case _ => "Neither One Nor Two" } getNumAsWord(1) // => "One" getNumAsWord(2) // => "Two" getNumAsWord(42) // => "Neither One Nor Two" Playing with Scala’s pattern matching
10. 10. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) Playing with Scala’s pattern matching
11. 11. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // create type alias Person type Person = (String, Int) def AskAge(person: Person) = person match { case ("Fibonacci", _) => "Fibonacci don't want to talk about his age" case (name, age) => s"\$name is \$age years old" } Playing with Scala’s pattern matching
12. 12. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // create type alias Person type Person = (String, Int) def AskAge(person: Person) = person match { case ("Fibonacci", _) => "Fibonacci don't want to talk about his age" case (name, age) => s"\$name is \$age years old" } AskAge(("Martin", 58)) // => "Martin is 58 years old" AskAge(("Fibonacci", 842)) // => "Fibonacci don't want to talk about his age" Playing with Scala’s pattern matching
13. 13. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // case class is like Struct in other languages case class Person(name: String, age: Int) def AskAge(person: Person) = person match { case Person("Fibonacci", _) => "Fibonacci don't want to talk about his age" case Person(name, age) => s"\$name is \$age years old" } Playing with Scala’s pattern matching
14. 14. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // case class is like Struct in other languages case class Person(name: String, age: Int) def AskAge(person: Person) = person match { case Person("Fibonacci", _) => "Fibonacci don't want to talk about his age" case Person(name, age) => s"\$name is \$age years old" } AskAge(Person("Martin", 58)) // => "Martin is 58 years old" AskAge(Person("Fibonacci", 842)) // => "Fibonacci don't want to talk about his age" Playing with Scala’s pattern matching
15. 15. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // List can be used as a case class def head[T](list: List[T]): T = list match { case List(head, tail) => head case Nil => throw new java.util.NoSuchElementException } Playing with Scala’s pattern matching
16. 16. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // List can be used as a case class def head[T](list: List[T]): T = list match { case List(head, tail) => head case Nil => throw new java.util.NoSuchElementException } // `head :: tail` is equivalent to `List(head, tail)` def head[T](list: List[T]): Option[T] = list match { case head :: tail => head case Nil => throw new java.util.NoSuchElementException } Playing with Scala’s pattern matching
17. 17. Scala Tutorial Pattern Match It can also unpack some structured data (e.g. tuple, case class, list) // List can be used as a case class def head[T](list: List[T]): T = list match { case List(head, tail) => head case Nil => throw new java.util.NoSuchElementException } // `head :: tail` is equivalent to `List(head, tail)` def head[T](list: List[T]): Option[T] = list match { case head :: tail => head case Nil => throw new java.util.NoSuchElementException } head(List(1,2)) // => Some(1) head(List()) // => None Playing with Scala’s pattern matching
18. 18. Scala Tutorial Pattern Match Here is an example of how you can use pattern matching to deal w/ different subtypes sealed trait Tree case class Branch(v: Int, left: Tree, right: Tree) extends Tree case class Leaf(v: Int) extends Tree def sum(t: Tree): Int = t match { case Branch(v, left, right) => v + sum(left) + sum(right) case Leaf(v) => v } val tree1 : Tree = Branch(1, Leaf(1), Leaf(2)) val tree2 : Tree = Branch(1, Branch(1, Leaf(1), Leaf(2)), Leaf(2)) sum(tree2) // => 7 sum(tree1) // => 4 Playing with Scala’s pattern matching
19. 19. Scala Tutorial Pattern Match Sometime we want to perform diff operation based on the object type. Here is the comparison between doing this via pattern match and via Java's instanceof method. // Compiler will complain before Integer does not have method `toChar` def f(x: Any): String = x match { case i: Int => "Integer: " + i.toChar(0) case s: String => "String: " + s case _ => "Unknown Input" } // Compiler won't complain and exception will raise at run-time public String f(Object x) { if (x instanceof Integer) { return "Integer: " + i.charAt(0) } // ..... } Playing with Scala’s pattern matching
20. 20. Use Recursion Question: Define the factorial function fact(n) , given n >= 0
21. 21. Use Recursion Question: Define the factorial function fact(n) , given n >= 0 // Imperative def fact(n: Int): Int = { var res = 1 for(i <- 1 to n) { res *= i } return res }
22. 22. Use Recursion Question: Define the factorial function fact(n) , given n >= 0 // Imperative def fact(n: Int): Int = { var res = 1 for(i <- 1 to n) { res *= i } return res } // Functional def fact(n: Int): Int = if (n == 0) 1 else n * fact(n-1)
23. 23. Use Recursion Why Recursion? 1. Easy to understand 2. Easy to prove its correctness
24. 24. Use Recursion Why Recursion? 1. Easy to understand 2. Easy to prove its correctness def fact(n: Int): Int = if (n == 0) 1 else n * fact(n-1)
25. 25. Use Recursion Why Recursion? 1. Easy to understand 2. Easy to prove its correctness def fact(n: Int): Int = if (n == 0) 1 else n * fact(n-1) Simply a function call using substitution fact(3) = if (3 == 0) 1 else 3 * fact(3-1) = 3 * fact(2) = 3 * (if (2 == 0) 1 else 2 * fact(2-1)) = ... = 3 * 2 * 1 * 1
26. 26. Use Recursion Question: Find the length of a list using recursion
27. 27. Use Recursion Question: Find the length of a list using recursion def length[T](list: List[T]): Int = list match { case Nil => 0 case head :: tail => 1 + length(tail) }
28. 28. Use Recursion Question: Find the length of a list using recursion def length[T](list: List[T]): Int = list match { case Nil => 0 case head :: tail => 1 + length(tail) } length(List()) // => 0 length(List(1,2)) // => 2
29. 29. Use Recursion Question: Find the length of a list using recursion def length[T](list: List[T]): Int = list match { case Nil => 0 case head :: tail => 1 + length(tail) } length(List()) // => 0 length(List(1,2)) // => 2 However, there is a performance issue w/ this function.
30. 30. Use Recursion Question: Find the length of a list using recursion def length[T](list: List[T]): Int = list match { case Nil => 0 case head :: tail => 1 + length(tail) } length(List()) // => 0 length(List(1,2)) // => 2 However, there is a performance issue w/ this function. val bigList = (1 to 40000).toList length(bigList) // => throw java.lang.StackOverflowError
31. 31. Use Tail Recursion Question: Define the factorial function fact(n) , given n >= 0
32. 32. Use Tail Recursion Question: Define the factorial function fact(n) , given n >= 0 import scala.annotation.tailrec def fact(n: Int): Int = { // @tailrec does not gurantee tail recursion // it simply raise an exception when there is not @tailrec def factHelper(n: Int, acc: Int): Int = if (n == 0) acc else factHelper(n-1, acc * n) factHelper(n, 1) }
33. 33. Use Tail Recursion Question: Find the length of a list using tail recursion
34. 34. Use Tail Recursion Question: Find the length of a list using tail recursion def length[T](list: List[T]): Int = { def lengthHelper(list: List[T], acc: Int): Int = list match { case Nil => acc case head :: tail => lengthHelper(tail, acc+1) } lengthHelper(list, 0) }
35. 35. Use Tail Recursion Question: Find the length of a list using tail recursion def length[T](list: List[T]): Int = { def lengthHelper(list: List[T], acc: Int): Int = list match { case Nil => acc case head :: tail => lengthHelper(tail, acc+1) } lengthHelper(list, 0) } length((1 to 40000).toList) // => 40000
36. 36. Use High-order Function Question: Define the factorial function fact(n) , given n >= 0
37. 37. Use High-order Function Question: Define the factorial function fact(n) , given n >= 0 def fact(n: Int): Int = (1 to n).fold(1) { (acc, x) => acc * x } // Or a shorter version def fact(n: Int): Int = (1 to n).fold(1) { _ * _ }
38. 38. Use High-order Function Common high-order functions for collections (e.g. array, list) include: (1 to 3).map(x => x * 2) // => Vector(2, 4, 6) (0 to 1).flatMap(x => (0 to 2).map(y => (x,y))) // => Vector((0,0), (0,1), (1,0), (1,1), (2,0), (2,1)) (1 to 10).filter(x => isPrime(x)) // => Vector(2, 3, 5, 7) (1 to 10).fold(0) { (acc, x) => acc + x } // => 55
39. 39. Use High-order Function These high-order functions recursively apply certain computation to a collection abstract class List[+T] { def map[U](f: T => U): List[U] = this match { case x :: xs => f(x) :: xs.map(f) case Nil => Nil } def filter(p: T => Boolean): List[T] = this match { case x :: xs => if (p(x)) x :: xs.filter(p) else xs.filter(p) case Nil => Nil } }
40. 40. Use High-order Function These high-order functions recursively apply certain computation to a collection abstract class List[+T] { def map[U](f: T => U): List[U] = this match { case x :: xs => f(x) :: xs.map(f) case Nil => Nil } def filter(p: T => Boolean): List[T] = this match { case x :: xs => if (p(x)) x :: xs.filter(p) else xs.filter(p) case Nil => Nil } } The actual implementation are different from this simplied version in order to: make them apply to arbitrary collections, not just lists make them tail-recursive on lists
41. 41. Use High-order Function Question: Find the longest word (assume there is ONLY one) val words = List("way", "jam", "complain", "second-hand", "elite") // Assume this method is given def findLongerWord(w1: String, w2: String) = { ... }
42. 42. Use High-order Function Question: Find the longest word (assume there is ONLY one) val words = List("way", "jam", "complain", "second-hand", "elite") // Assume this method is given def findLongerWord(w1: String, w2: String) = { ... } // Tail Recursion def findLongestWord(words: List[String]): String = { @tailrec def go(words: List[String], longestWord: String) = words match { case Nil => longestWord case word :: otherWords => go(otherWords, findLongerWord(longestWord, word)) } go(words, "") }
43. 43. Use High-order Function Question: Find the longest word (assume there is ONLY one) val words = List("way", "jam", "complain", "second-hand", "elite") // Assume this method is given def findLongerWord(w1: String, w2: String) = { ... } // Use High-order Function def findLongestWord(words: List[String]): String = words.fold("") { (acc, x) => findLongerWord(acc, x) }
44. 44. Use High-order Function Question: Find the longest word (assume there is ONLY one) val words = List("way", "jam", "complain", "second-hand", "elite") // Assume this method is given def findLongerWord(w1: String, w2: String) = { ... } // Use High-order Function def findLongestWord(words: List[String]): String = words.fold("") { (acc, x) => findLongerWord(acc, x) } def findLongestWord(words: List[String]): String = words.fold("")(findLongerWord)
45. 45. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department
46. 46. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department // Here are the data model // `case class` is Struct (kind of) in Scala case class Employee(name: String) case class Department(name: String, employees: List[Employee]) case class Company(name: String, revenue: BigInt, departments: List[Department]) // Association // A company has many departments // A department has many employees
47. 47. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department val companies = loadDataFromDatabase() // Works but not very readable val res = companies .filter(company => company.revenue > 5000000) .flatMap(company => company.departments. filter(d => d.name == "marketing"). flatMap(d => d.employees.map(e => e.name)) )
48. 48. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department // Easier to read but // this helper function is too specific to be reused def getMarketingEmployeeName(company: Company): List[String] = { company.departments. filter(d => d.name == "marketing"). flatMap(d => d.employees.map(e => e.name)) } val res = companies .filter(company => company.revenue > 5000000) .flatMap(company => getMarketingEmployeeName(company))
49. 49. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department // More readable but less efficient - // create unnecessary intermediate result val res = companies .filter(c => c.revenue > 5000000) .flatMap(c => c.departments) .filter(d => d.name == "marketing") .flatMap(d => d.employees) .map(e => e.name)
50. 50. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department // As efficient as the 1st/2nd one and // as readable as the 3rd one for { c <- companies if c.revenue > 5000000 d <- c.departments if d.name == "marketing" e <- d.employees } yield e.name
51. 51. Use High-order Function Question: Get the name of employees who work at a company w/ more than 5 million revenue who work at the marketing department // As efficient as the 1st/2nd one and // as readable as the 3rd one for { c <- companies if c.revenue > 5000000 d <- c.departments if d.name == "marketing" e <- d.employees } yield e.name For expression will be converted into a set of map , flatMap , withFilter (or filter ) operations.
52. 52. NOTE: Skip Lazy Evaluation and Infinite List if prev section takes too long
53. 53. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13
54. 54. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13 // Imperative // This solution is error-prone def nthPrime(n: Int): Int = { var k = 2 var counter = 0 while (counter < n) { k += 1 if (isPrime(k)) { counter += 1 } } k }
55. 55. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13 // Use higher-order function def nthPrime(n: Int, upperBound: Int): Int = { (1 to upperBound).filter(isPrime)(n) }
56. 56. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13 But ... how do we know the value of upperBound
57. 57. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13 But ... how do we know the value of upperBound // `upperBound` is too small => exception nthPrime(50, 100) // `upperBound` is too large => run forever nthPrime(50, Int.MaxValue) // `upperBound` has the perfect value, which will be the answer itself nthPrime(50, 229)
58. 58. Lazy Evaluation and Infinite List Question: Find nth prime number nthPrime(0) // => 2 nthPrime(1) // => 3 nthPrime(5) // => 13 // http://stackoverflow.com/questions/13206552/scala-rangex-int-maxvalue-vs-stream-fromx // Use Stream for Lazy Evaluation def nthPrime(n: Int): Int = primeNums(n) // A stream of prime number def primeNums: Stream[Int] = Stream.from(2).filter(isPrime) primeNums.take(3) // => Stream(2, ?) primeNums.take(3).toList // => List(2,3,5)
59. 59. Lazy Evaluation and Infinite List Question: Get the nth fib number // Assume n > 0 def fib(n: Int): Int = { if (n == 0) 1 else if (n == 1) 1 else fib(n-1) + fib(n-2) } def fib(n: Int): Int = { def fibNums(n1: Int, n2: Int): Stream[Int] = { n1 #:: fibNums(n2, n1+ n2) } fibNums(1, 1)(n) }
60. 60. Lazy Evaluation and Infinite List Question: Create the prime number sequence using Sieve of Eratosthenes def sieve(s: Stream[Int]): Stream[Int] = s.head #:: sieve(s.tail filter (_ % s.head != 0)) val primes = sieve(Stream.from(2)) primes.take(5).toList // => List(2, 3, 5, 7, 11)
61. 61. Lazy Evaluation and Infinite List Question: How to implement sqrt method recursively def sqrtStream(x: Double): Stream[Double] = { def improve(guess: Double) = (guess + x / guess) / 2 // TODO: What if no `lazy` lazy val guesses: Stream[Double] = 1 #:: (guesses map improve) guesses }
62. 62. Lazy Evaluation and Infinite List Question: How to implement sqrt method recursively def sqrtStream(x: Double): Stream[Double] = { def improve(guess: Double) = (guess + x / guess) / 2 // TODO: What if no `lazy` lazy val guesses: Stream[Double] = 1 #:: (guesses map improve) guesses } sqrtStream(4).take(8).toList // => List(1.0, 2.5, 2.05, 2.000609756097561, 2.0000000929222947, 2.000000000000002, 2.0, 2.0) def isGoodEnough(guess: Double, x: Double) = math.abs((guess * guess - x) / x) < 0.0001 sqrtStream(4).filter(isGoodEnough(_, 4)).take(1).head // => 2.0000000929222947 Coursera: Functional Programing Design in Scala
63. 63. Summary Here are the Functional Programing approaches that replaces the imperative for or while loop: Recursion Tail Recursion High-order Function Lazy Eval and Infinite List