Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Functional Programming in Scala...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Introduction to Functional Programming in Scala Part 1

Avatar for Melvic Ybanez

Melvic Ybanez

January 24, 2015

More Decks by Melvic Ybanez

Other Decks in Programming

Transcript

  1. Introduction to Functional Programming in Scala • About Functional Programming

    • What is FP? • Functional vs Imperative • Purity • Immutability • Referential Transparency • Side Effects • Recursion • Tail-recursion • Lambda Expressions • Higher-order Functions • Functions as parameters to functions • Functions as returned values from functions • Collections • Currying and Partial Applications • Laziness • Functors (and Applicatives) • Monoids • Monads Part 1 Part 2 Advanced (Just for further readings) by Melvic Ybañez
  2. What is Functional Programming Functional Programming is the style of

    programming which models computations as the evaluation of expressions Source: HaskellWiki
  3. Functional vs Imperative Imperative programming – You tell the computer

    what to do – Programs are composed mostly of statements – Mutations everywhere Functional Programming – You tell the computer what stuff is – Programs are composed mostly of expressions – Immutability is encouraged (if not enforced) – Functions are first-class citizens
  4. Immutability If you say a is 5, you can't just

    change your mind and say it's something else later on. After all, you said it was 5. (What are you, some kind of liar?) Source: Learn You A Haskell For Great Good!
  5. Mutable variables in Scala var x: Int = 7 //>

    x : Int = 7 x = 10 x //> res0: Int = 10 // with type inference, you can ommit the type (: Int)... var y = 9 //> y : Int = 9 y = x + 1 y //> res1: Int = 11
  6. The val keyword // All of these variables can't be

    reassigned val x: Int = 7 //> x : Int = 7 val y = x + 1 //> y : Int = 8 val xs = Array(1, 2, 3) //> xs : Array[Int] = Array(1, 2, 3) // Arrays are mutable. So you even if you can't reassign xs, // you can still mutate it xs(1) = 89 xs //> res0: Array[Int] = Array(1, 89, 3)
  7. Val + Immutable data structures // val + immutable data

    structures = recommended way to // implement immutability in Scala // You can't reassign xs because it's a val (not a var) // You also can't mutate it because it's a List, not an Array. // Lists are immutable data structures in Scala val xs = List(1, 2, 3) val str = "Strings in Scala are immutable too, just like in Java" val dict = Map("Scala" -> ".scala", "Java" -> ".java") val imATuple = (777, "777")
  8. Referential Transparency A referentially transparent function, when given the same

    list of parameters, should always return the same output.
  9. Non-referentially Transparent Functions var foo = 0 def increaseFooBy(x: Int):

    Int = { foo += x foo // you can ommit the return keyword (last expression = returned value) } // BTW, in most cases, you can ommit the return type of the function (like val) // Told you, type inference is your friend! def add1ToFooAndDoubleIt() = { increaseFooBy(1) + increaseFooBy(1) } // Oh and you can even remove the parentheses if you don't have any parameters. // It would look like a val (Functions are values. Consistency!) def multiplyTheResultBy5 = add1ToFooAndDoubleIt * 5
  10. Referentially Transparent Functions def helloWorld = "HelloWorld" //> helloWorld: =>

    String def sum(x: Int, y: Int) = x + y //> sum: (x: Int, y: Int)Int // You can call helloWorld as many times as you want. // You'll get the same results. helloWorld //> res0: String = HelloWorld helloWorld //> res1: String = HelloWorld // Call sum with the same parameters (same results are expected) sum(1, 2) //> res2: Int = 3 sum(1, 2) //> res3: Int = 3
  11. So why bother implementing RT? def myName = "Melvic Ybanez"

    // This expression... "My name is " + myName // ...is exactly the same as this: "My name is Melvic Ybanez" // hard-coded name (sigh) // because the myName function and the "Melvic Ybanez" string are interchangeable. // You can replace myName directly with "Melvic Ybanez" // without changing the meaning of the program. // With this, you can reason with your program a lot easier (No surprises!)
  12. Side Effects Side effects happen when some things in some

    places are being changed as a result of executing an operation, or when an operation causes the program to interact with the outside world. Examples include the following: – Mutating a variable – Printing to the screen – Writing data to a database – Changing a User Interface – Getting input from a keyboard
  13. Side-effecting Functions // A function that doesn't return anything is

    useless, unless it has side-effects. // Unit functions, therefore, encourage side effects. def processCountryName: Unit = { countryName = io.StdIn.readLine // access outer variable if (countryName == "Elephant") println("That's not a country!") else println("Everything else is a country name, you know.") } // A unit function doesn't need the '=' sign, actually def displaySum(xs: List[Int]) { var sum = 0 for (x <- xs) sum += x println("The sum is " + sum) }
  14. Side-effect-free Functions // if-expression (not statement) def increaseByOneIfSmall(x: Int) =

    if (x < 100) x + 1 else x // pattern matching in action def operate(x: Int, y: Int, operator: Char) = operator match { case '+' => x + y case '-' => x - y case '*' => x * y case '/' => x / y case _ => Math.pow(x, y) // catch-all pattern } // for-expression. This is the same as python's list-comprehensions def getSmallLengths(strs: List[String]) = for { str <- strs len = str.length if len < 4 } yield len
  15. Recursion Some functions, in some cases, are applicable inside their

    own definitions. Such functions are known to be recursively defined.
  16. Basic List operators //empty list Nil > res0: scala.collection.immutable.Nil.type =

    List() // cons operator 1 :: List(2, 3) //> res1: List[Int] = List(1, 2, 3) // Cons is right associative. 'a' :: 'b' :: 'c' :: Nil == 'a' :: ('b' :: ('c' :: Nil)) //> res2: Boolean = true // concat operator List(1, 3) ++ List(5, 3) //> res3: List[Int] = List(1, 3, 5, 3)
  17. Recursion // Note: Return types are required for recursive functions

    // compute the factorial of a number def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1) // get the sum of all the items in the list def sum(xs: List[Int]): Int = xs match { case Nil => 0 case h :: t => h + sum(t) }
  18. More recursive functions // get the length of the list

    def length[A](xs: List[A]): Int = xs match { case Nil => 0 case h :: t => 1 + length(t) } // reverses a list def reverse[A](xs: List[A]): List[A] = xs match { case Nil => Nil case h :: t => reverse(t) ++ List(h) }
  19. What's the problem? factorial(5) = 5 * factorial(4) = 5

    * (4 * factorial(3)) = 5 * (4 * (3 * factorial(2))) = 5 * (4 * (3 * (2 * factorial(1)))) = 5 * (4 * (3 * (2 * (1 * factorial(0))))) = 5 * (4 * (3 * (2 * (1 * 1)))) = 5 * (4 * (3 * (2 * 1))) = 5 * (4 * (3 * 2)) = 5 * (4 * 6) = 5 * 24 = 120 You'll get a “stack overflow error” with large inputs!
  20. What's the Solution? factorial(5) = factorial(5, 1) = factorial(4, 5

    * 1) = factorial(3, 4 * 5) = factorial(2, 3 * 20) = factorial(1, 2 * 60) = factorial(0, 1 * 120) = 120 It doesn't look as expensive as the previous approach, right?
  21. Enter Tail-Recursion If the last action of a function consists

    of calling a function (which may be the same), one stack frame would be sufficient for both functions. Such calls are called tail-calls. Source: Functional Programming Principles in Scala
  22. Tail-recursive Functions // tail-recursive factorial def factorial1(n: Int, fact: Int

    = 1): Int = if (n == 0) fact else factorial1(n - 1, n * fact) // This is a better version since it prevents “information leakage” // (i.e it doesn't make sense to expose the second param to the outside world). // The presence of the annotaion is not required but is recommended // for compile-time checking for tail-calls def factorial2(n: Int) = { @scala.annotation.tailrec def recurse(n: Int, acc: Int): Int = if (n == 0) acc else recurse(n - 1, n * acc) recurse(n, 1) }
  23. More Tail-Recursive Functions import annotation.tailrec def sum1(xs: List[Int]) = {

    @tailrec def recurse(xs: List[Int], acc: Int): Int = xs match { case Nil => acc case h :: t => recurse(t, h + acc) } recurse(xs, 0) } def length1[A](xs: List[A]) = { @tailrec def length(xs: List[A], len: Int): Int = xs match { case Nil => len case _ :: t => length(t, len + 1) } length(xs, 0) }
  24. More...more // This is more efficient than the non-tail-recursive version

    // that we wrote earlier since cons (::) peforms better than the // concatenation operator (++). def reverse1[A](xs: List[A]) = { @tailrec def reverse(xs: List[A], acc: List[A]): List[A] = xs match { case Nil => acc case h :: t => reverse(t, h :: acc) } reverse(xs, Nil) }
  25. Lambda expression Lambda expressions refer to anonymous functions. They are

    usually used as arguments or return values to/from higher-order functions. In Scala, you can also store lambdas to vals.
  26. Val functions in Scala // a function that accepts two

    ints and return an int val max: (Int, Int) => Int = (a, b) => if (a > b) a else b // same function, but this time the type of max1 is inferred val max1 = (a: Int, b: Int) => if (a > b) a else b // A multiline function. You can remove the parens if there's only one param. // You can't use generics with val functions. So let's stick with Ints here. val concat = { (xs: List[Int], ys: List[Int]) => def recurse(xs: List[Int], acc: List[Int]): List[Int] = xs match { case Nil => acc case h :: t => recurse(t, h :: acc) } recurse(xs.reverse, ys) }
  27. Pretty cool, huh? Lambdas (and the other FP goodness) will

    return with Higher-order Functions in the next part of this tutorial. To be continued