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

Introduction to Functional Programming in Scala...

Introduction to Functional Programming in Scala Part 2

Avatar for Melvic Ybanez

Melvic Ybanez

February 01, 2015

More Decks by Melvic Ybanez

Other Decks in Programming

Transcript

  1. Higher-Order Functions A higher-order function is a function that accepts

    functions as parameters or return a function (or both). Higher-order functions are the bread and butter of functional programming.
  2. Implementing a function that takes a function // A function

    that takes another function, and // apply that function to a and b. def compute(f: (Int, Int) => Int) = { val (a, b) = (10, 5) // pattern-match a tuple (a=10, b=5) f(a, b) } > compute: (f: (Int, Int) => Int)Int // you can call the compute function like this def sum(a: Int, b: Int) = a + b //> add: (a: Int, b: Int)Int compute(sum) //> res0: Int = 15
  3. Passing lambdas to our HOF // pass a lambda expression

    that subtracts b from a compute((a, b) => a - b) //> res1: Int = 5 // if the function expects 1 param, you can replace () with {} compute { (a, b) => a * b } //> res2: Int = 50 // pass a multiline lambda compute { (a, b) => def negate(n: Int) = -n negate(a) / negate(b) } //> res3: Int = 2 // Use some sort of a wildcard. This is convenient to use // if you don't care about the names of the parameters. compute { _ + _ } //> res4: Int = 15
  4. Who doesn't like code-reuse? val compareHelloAndWorld = "hello" == "world"

    val compareHelloAndCebu = "hello" == "cebu" val compareHelloAndCity = "hello" == "city" val compareHiAndHo = "hi" == "ho" val compareHiAndHmm = "hi" == "hmm" // "hello" == and "hi" == both appeared more than once. // What if we replace "hello" == with "thank you" != or // with someVeryAwesomeUserDefinedComparisonFunction(“hello”)?
  5. Implementing a function that returns a function // This function

    accepts 1 param and returns a function (a lambda, to be more // precise) that accepts 1 param. Both params are being compared using ==. def compareStrings(str1: String) = { str2: String => str1 == str2 } // A function that accepts a string param and compares it to "hello" val compareHello = compareStrings("hello") val compareHelloAndCebu = compareHello("cebu") val compareHelloAndCity = compareHello("city") val compareHi = compareStrings("hi") val compareHiAndHo = compareHi("ho") val compareAppleAndOrange = compareStrings("apple")("orange")
  6. • Functional languages like Haskell and Scala have really small

    cores. • In Scala, many of the things that look like language features are actually just higher-order functions. • Python decorators are also just higher-order functions in disguise. HOFs offer flexibility and expressiveness
  7. A useful example: filter function // A HOF that takes

    a list and a function that serves as a predicate. // It returns a new list containing only elements for which the // predicate returns true. def filter[A](xs: List[A], p: A => Boolean) = { @scala.annotation.tailrec def recurse(xs: List[A], acc: List[A]): List[A] = xs match { case Nil => acc case h :: t => val newAcc = if (p(h)) h :: acc else acc recurse(t, newAcc) } recurse(xs, Nil).reverse }
  8. Using our filter function val xs = (0 to 5).toList

    > List(0, 1, 2, 3, 4, 5) val even = filter(xs, { x: Int => x % 2 == 0})// > List(0, 2, 4) val smallLengths = filter(List("because", "english"), { str: String => str.length < 8 }) //> List(because, english) case class Person(name: String, address: String, age: Int) val giantSmilers = List( Person("Pat", "Hidden Village of Konoha", 100), Person("Sonito", "Soul Society", 101), Person("Murat", "New World Grandline", 102), Person("Melvic", "Anywhere with Pringles and Coffee", 105)) val namedMelvic = filter(giantSmilers, { gs: Person => gs match { case Person("Melvic", _, _) => true case _ => false }}) > List(Person(Melvic,Anywhere with Pringles and Coffee,105))
  9. Currying • In many cases, it is useful to transform

    the application of a function that takes multiple arguments into a series of function applications where each of the functions takes only 1 argument. • Mathematically speaking, functions that take multiple arguments are not really needed. • In some FP languages, like Haskell, functions are curried by default.
  10. Wait. That sounds like a load of crap! I know,

    I know. Everything will be a lot clearer in a few minutes though (hopefully). (Note: Make sure you now understand how higher-order functions work.)
  11. Improving our filter function // A higher-order function that takes

    a list and returns // another higher-order function (woah!). The returned function // will take a function that will serve as the predicate, // and return a new list containing only elements for which the // predicate returns true. def filter[A](xs: List[A]) = { p: (A => Boolean) => @scala.annotation.tailrec def recurse(xs: List[A], acc: List[A]): List[A] = xs match { case Nil => acc case h :: t => val newAcc = if (p(h)) h :: acc else acc recurse(t, newAcc) } recurse(xs, Nil).reverse }
  12. Using the new filter function // Now, the type inference

    works better // See the use of _ instead of x: Int => // where x is a param name val xs = (0 to 5).toList val even = filter(xs) { x: Int => x % 2 == 0 } val even1 = filter(xs)(_ % 2 == 0) // we don't care about the param's name // We can now reuse filter(xs). Remember compareHello? // Note: filterXs is a higher-order function too val filterXs = filter(xs) val greaterThan1 = filterXs(x => x > 1) val lessThan2 = filterXs(_ < 2) val sevens = filterXs(_ == 7)
  13. Our new filter is just better! case class Person(name: String,

    address: String, age: Int) val giantSmilers = List( Person("Pat", "Hidden Village of Konoha", 100), Person("Sonito", "Soul Society", 101), Person("Murat", "New World Grandline", 102), Person("KerwinJ", "Fairy Tail Guild", 104), Person("Melvic", "Anywhere with Pringles and Coffee", 105)) // Pattern matching becomes even less verbose. // Compare it to the previous version. val namedMelvic = filter(giantSmilers) { case Person("Melvic", _, _) => true case _ => false }
  14. We can improve things even further • Our compareStrings and

    filter functions both return functions, and end up taking only one argument. It's not coincidence. It's just so common in FP. • In fact, there are languages whose functions behave like that all the time. • Behave like what? Like Curried Functions
  15. Curried filter function // The curried filter function with 2

    parameter list. The first one // takes a list, and the second takes the predicate. This one is // just the same as the version 2 of filter except that this is a lot // easier to read because it doesn't look like you're returning a // function at all. def filter[A](xs: List[A])(p: A => Boolean) = { @scala.annotation.tailrec def recurse(xs: List[A], acc: List[A]): List[A] = xs match { case Nil => acc case h :: t => val newAcc = if (p(h)) h :: acc else acc recurse(t, newAcc) } recurse(xs, Nil).reverse }
  16. The same, huh? I lied LOL There's actually another difference

    between the version 2 and version 3 (curried) of filter aside from readability. You can't do this anymore: val filterXs = filter(xs) because filter needs another argument. Don't expect it to just return another function like version 2 does, man!
  17. Partial Application • Partial application happens when you apply a

    function to a fewer number of arguments than it's supposed to take. • When you partially apply a function, you'll receive a new function that takes as many parameters as you didn't specify.
  18. Partially apply filter // This call returns a new function

    the takes a predicate. // Note: The _ after the first parameter list indicates that // a partial application is happening. val filterXs = filter(xs)_ // Everything else is the same as before val greaterThan1 = filterXs(x => x > 1) val lessThan2 = filterXs(_ < 2) val sevens = filterXs(_ == 7)
  19. Last Example: map function def map[A, B](xs: List[A])(f: A =>

    B) = { def recurse(xs: List[A], acc: List[B]): List[B] = xs match { case Nil => acc case h :: t => recurse(t, f(h) :: acc) } recurse(xs, Nil).reverse } map(List(1, 2, 3)) { x => x.toString } /> res0: List[String] = List(1, 2, 3) map(List("apples", "oranges"))(_.length) /> res1: List[Int] = List(6, 7) case class Student(id: String, name: String) val students = List(Student("0001", "Foo"), Student("0002", "Bar")) map(students)(_.id) > res2: List[String] = List(0001, 0002)
  20. We're done (yey)! Persistent Data Structures and Laziness (and actually,

    a lot of other FP concepts) were not included in the discussion. Illustrations on some Advanced Topics: Functors, Applicatives, And Monads In Pictures