Scala Quick Notes :: Part - 7
Bhaskar S | 02/15/2015 |
Overview
In this part of the series, we will continue to look at the Functional capabilities of Scala such as Partially Applied functions, Curried functions, Partial functions, and Recursive functions.
Hands-on With Scala - VII
The following is the Scala program named Sample22.scala:
Executing the program Sample22.scala results in the output:
modulus(5, 2) = 1 mod2 = <function1> mod2(5) = 1 modulus2 = <function1> modulus2(5) = 1 compute(2, 3, 4) = 1.25 average = <function2> average(2, 3) = 2.50 average2 = <function2> average2(2, 3) = 2.50
The following section explains some of the aspects of the Scala program Sample22.scala:
When invoking a function, if only some input parameters are provided, the result is a Partially Applied function. A Partially Applied function can be assigned to a variable (like any other object since functions are also objects). The assigned variable is often referred to as a Function Value which can later be used like a function. By passing the missing input parameters to the Function Value, the original function execution can be completed
Ex: val mod2 = modulus(_: Int, 2)
Partially Applied functions are useful when one or more of the parameters are not unknown at the point of invokation and the original function execution needs to be delayed until later
In the above example, the Partially Applied function mod2 is of type Function1
A Scala type Function1 is a trait for a function that takes one input parameter and produces some result. In the case of mod2, it a function that takes in input of type Int and produces a result of type Int
Ex: printf("mod2 = %s\n", mod2)
In the above example, the Partially Applied function average is of type Function2
A Scala type Function2 is a trait for a function that takes two input parameters and produces some result. In the case of average, it a function that takes two inputs of type Double and produces a result of type Double
Ex: printf("average = %s\n", average)
Scala defines function traits from Function1 (a function that takes one input parameter and produces a result) to Function22 (a function that takes twenty-two input parameters and produces a result)
In the above example, the function modulus2 simulates the behavior of the function mod2 by creating a function object by mixing in the trait for Function1 and overriding the apply method. A function call in Scala invokes the apply method under the covers
The following is the Scala program named Sample23.scala:
Executing the program Sample23.scala results in the output:
logmsg('INFO')('Information 1') = INFO :: Information 1 infolog('Information 2') = INFO :: Information 2 warnlog('This is a Warning') = *WARN* :: This is a warning infoLogSize20('This is long information') = INFO :: This is long
The following section explains some of the aspects of the Scala program Sample23.scala:
Currying is the technique of transforming a function that takes multiple arguments into a sequence of functions that take a single argument
Use the syntax def FUNCTION-NAME(PARAM-1: T1)(PARAM-2: T2)...(PARAM-N: TN) = { FUNCTION BODY } to define a Curried function in Scala
Ex: def logMsg(a: String)(b: String) = a + " :: " + b
A Curried function allows for specialization of a generic Higher-Order function. Think of it as a way of deriving a new function from a generic Higher-Order function with a captured variable using Closure
Ex: val warnlog = logMsg("*WARN*") _
In our example, warnlog is a Curried function that has captured the literal string *WARN* for the first parameter
The following is the Scala program named Sample24.scala:
Executing the program Sample24.scala results in the output:
dayOfWeek(1) = Sun dayOfWeek(5) = Thu dayOfWeek2(1) = Sun dayOfWeek2(5) = Thu dayOfWeek3(2) = Some(Mon) dayOfWeek3(0) = None
The following section explains some of the aspects of the Scala program Sample24.scala:
A Partial function is a function that will produce an output value for only a subset of input values. As an example, given a day of the week as a number from 1 through 7, output the day of the week as a 3-letter word starting from Sun through Sat
Use the Scala trait PartialFunction to define a Partial function.
Override the methods apply and isDefinedAt
Ex: val dayOfWeek = new PartialFunction[Int, String] { ... }
One can lift a PartialFunction so that it produces defined values as a Some or an undefined value as a None using Scala Option
Ex: val dayOfWeek3 = dayOfWeek.lift
The following is the Scala program named Sample25.scala:
Executing the program Sample25.scala results in the output:
factorial(5) = 120 factorial(10) = 3628800 factorial(100) = 9332621544394415268169923885626670049071596826438162146859296389521... factorial(1000) = 402387260077093773543702433923003985719374864210714632543799910429... factorial2(1000) = 40238726007709377354370243392300398571937486421071463254379991042... factorial2(10000) = 2846259680917054518906413212119868890148051401702799230794179994...
The following section explains some of the aspects of the Scala program Sample25.scala:
A Recursive function is a function that invokes itself (with one or more checks to avoid infinite loop) to produce a desired end result
Ex: def factorial(n: Int): BigInt = { if (n < 2) 1 else n*factorial(n-1) }
Recursive functions are very useful as each iteration of the function stores the parameters as well as local data on the function stack without the need for any mutable data structures.
The flip side of Recursive functions is that one can run into a Stack Overflow error if the recursive calls are too deep (exhausting the allocated stack space).
This is usually the case with the classic Recursive function (also referred to as a Head Recursive function) when the current computation involves the result from the next iteration of the function such as n * factorial(n-1)
To avoid the pitfalls of over using the stack space and running into the dreaded Stack Overflow error, one should design the Recursive function to use Tail Recursion.
In a Tail Recursive function, each function call invocation does not create new function stack space but instead re-uses the current function stack space. This is achieved by adding an accumulator parameter to the function call (the accumulator stores the computation from the successive iterations such as n * accumulator) without the need for the result from the next invocation of the function
Use the annotation @tailrec to mark a Recursive function as using Tail Recursion
Ex: def factorial2(n: Int): BigInt = { @tailrec def factorial2TailRec(n: Int, accumulator: BigInt): BigInt = { if (n < 2) accumulator else factorial2TailRec(n-1, n*accumulator) } factorial2TailRec(n, 1) }
References