Source

scala-robustness-brevity-ftw-talk / functions.scala

Full commit
// Lets look at a simple function declaration

def add(i: Int, j: Int) = i + j

// Note: 
//  1. return type is automatically inferred
//  2. No semicolons
//  3. Even braces not mandatory
//  4. return statement not required - last expression is implicitly returned


// Functions can even be anonymous and assigned to a val/var

val increment = (x: Int) => x + 1
println(increment(5))

// Note: 
//  An anonymous function was assigned to a variable
//  Analogous to function pointers in C

// Functions can be passed to other functions

def square(i: Int) = i * i

def printComputed(number: Int, f: (Int)=> Int) = 
    println("Computed Value is " + f(number))

printComputed(3, square)

// Functions could be passed anonymously too

printComputed(4, (n: Int) => n * n)

// Functions can be partially applied 

val printSquareOf = printComputed(_:Int,square)
printSquareOf(5)

// Functions can be curried too

def printComputed2(number: Int)(f: (Int)=> Int) = 
    println("Computed Value is " + f(number))

printComputed2(6)(square)

// And if the last value is a function, its easy to
// convert it into an anonymous code block

printComputed2(7) { n =>
    n * n
}

// Functions can have default arguments

def computeTax(basicAmount: Float, taxPct:Int = 10) = {
    basicAmount * (1 + (taxPct.toFloat/100))
}

println(computeTax(50))
println(computeTax(75,8))

// And named arguments too

println(computeTax(taxPct = 15, basicAmount = 100))

// You could also pass variable arguments

def addAll(params: Int*) = {
    // This is not the preferred way.
    // especially given that we are using a mutable variabl
    var sum = 0
    for (param <- params) sum += param
    sum
}

println(addAll(3,5,7,9))

// Functions can be composed
// earlier :
//  def square(i: Int) = i * i
//  val increment = (x: Int) => x + 1
// What if we want to compute the square of the increment

println(square(increment(3)))
println(increment(square(3)))

println(increment.andThen(square)(3))
println(increment.compose(square)(3))

// Partial functions (as opposed to total functions)
// Sometimes functions are applicable only for a subset of 
//   values
// A square root function is defined only positive inputs
// Or a division function is not defined for divisor being zero

val safeSquareRoot: PartialFunction[Double, Double] = {
    case num: Double if num > 0.0 => math.sqrt(num)
}

println(safeSquareRoot.isDefinedAt(1.69))
println(safeSquareRoot(1.69))
println(safeSquareRoot.isDefinedAt(-5.0))
// The following will raise an exception before attempting to
// compute the square root
try {
    println(safeSquareRoot(-5.0))
} catch {
    case e: Exception => println(e)
}

// The case statement we saw above is referred to as pattern matching
// Usually you do not need to explicitly declare the PartialFunction type
// it is inferred when the pattern match is a part of a code block

// Functions are also objects, with the apply method defined as the function body

object multiply extends Function2[Int,Int,Int] {
    def apply(x: Int, y: Int) : Int = x * y
}

println(multiply(3,4))