• 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
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
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!
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
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)
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")
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
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
// 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!)
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
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) }
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
// 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) }
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) }
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
= 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) }
@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) }
// 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) }
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) }