This extends Q1 – Q5 Scala Functional Programming basics interview questions & answers
Q6. What is a curried function in Scala?
A6. Currying is the technique of transforming a function with multiple arguments into a function with just one argument, and the other arguments are curried.
Currying is when you break down a function that takes multi-group parameters into a series of functions that take part of the arguments. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.mytutorial object CurryingInScala extends App { //normal function def add(x: Int, y: Int): Int = { return x + y } println(add(5, 6)) // 11 //curried function by separating out the arguments def addCurried(x: Int)(y: Int): Int = { return x + y } println(addCurried(1) _) // returns a function println((addCurried(1) _).apply(5)) // returns 6 println(addCurried(1)(2)) //returns 3 } |
“(x: Int, y: Int)” is split into “(x: Int)(y: Int)”.
Output:
1 2 3 4 5 6 |
11 <function1> 6 3 |
“_” is an anonymous function place holder parameter. The above curried function was written the shorter way, and can be written as shown below by converting (Int, Int) => Int to Int => (Int => Int)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.mytutorial object CurryingInScala extends App { //curried (i.e. partial) function by separating out the arguments def addCurried(x: Int): (Int => Int) = { return (y: Int) => { //return is optional println("y = " + y); x + y } } println(addCurried(1)) // returns a function println((addCurried(1)).apply(5)) // returns 6 println(addCurried(1)(2)) //returns 3 } |
Output:
1 2 3 4 5 |
<function1> 6 3 |
The curried function can be simplified as shown below
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.mytutorial object CurryingInScala extends App { //curried (i.e. partial) function by separating out the arguments def addCurried(x: Int) = (y: Int) => x + y println(addCurried(1)) // returns a function println((addCurried(1)).apply(5)) // returns 6 println(addCurried(1)(2)) //returns 3 } |
Scala REPL example 1:
(Int,Int) => Int. A function takes two “int” values and returns an “int” value.
1 |
$ scala |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
scala> def addCurried(x: Int)(y: Int): Int = { | return x + y | } addCurried: (x: Int)(y: Int)Int scala> val add_5 = addCurried(5)_ add_5: Int => Int = $$Lambda$1118/2021540695@392781e scala> add_5(6) res0: Int = 11 scala> val add_3 = addCurried(3)_ add_3: Int => Int = $$Lambda$1126/1605872540@6734ff92 scala> add_3(6) res1: Int = 9 scala> |
Note: _ after addCurried(5) is an eta expansion“. It converts methods into functions (i.e. Function1, Function2, etc).
So, you can think of addCurried(5) _ to be converted to
1 2 3 4 5 6 7 |
val f = new Function1[Int, Int] { def apply(y: Int): Int = addCurried(5)(y) } println(f(6)) // returns 11 |
You can try the full code as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
object FunctionsInScala3 extends App { def addCurried(x: Int)(y: Int): Int = { return x + y } val add_5 = addCurried(5) _ println(add_5) println(add_5(6)) val f = new Function1[Int, Int] { def apply(y: Int): Int = addCurried(5)(y) } println(f(6)) } |
Scala REPL example 2:
Int => (Int => Int). A function that takes an “int” value, and returns another function that takes an “int” as input and returns an “int”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
scala> def addCurried(x: Int) = (y: Int) => x + y addCurried: (x: Int)Int => Int scala> val add_5 = addCurried(5) add_5: Int => Int = $$Lambda$1141/2008165125@4fef5792 scala> add_5(6) res2: Int = 11 scala> add_5(9) res3: Int = 14 scala> |
Q. How will you write a generic function to convert any binary functions as shown below into a curried function?
1 2 3 |
def add(a: Int, b: Int) = a + b // (Int, Int) => Int def multiply(a: Int, b: Int) = a * b // (Int, Int) => Int |
A. Basically, you need function that converts (Int, Int) => Int to Int => (Int => Int).
1 2 3 4 5 6 7 8 9 10 |
def curryingBinOp[T](f: (T, T) => T ): T => (T => T) = { //T => (T=>T) def curry(x: T): T => T = { (y: T) => f(x,y) } curry } |
You can now apply it as shown below:
1 2 3 4 5 6 7 8 9 10 11 |
def add(a: Int, b: Int) = a + b // (Int, Int) => Int def multiply(a: Int, b: Int) = a * b // (Int, Int) => Int val addition = curryingBinOp(add) val add5 = addition(5) println(add5(6)) // prints 11 val mult = curryingBinOp(multiply) val mult5 = mult(5) println(mult5(6)) // prints 30 |
Q7. What is a closure in Scala?
A7. A closure is a function, whose return value depends on the value of one or more variables declared outside this function. These are also known as free variables.
Closure = function + environment
A function has an implicit environment that can be used to access variables lexicographically like “more”.
A closure will have one or more free variables as variable “more” shown below. If “more” is declared with “val“, which means it is immutable, and becomes referentially transparent & will be a pure function. If “more” is defined as a “var”, then addCurried(..) becomes an impure function.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.mytutorial object ClosuresInScala extends App{ val more = 20; //curried function has a reference to variable "more" //outside the function but in the enclosing scope" def addCurried(x: Int) = (y: Int) => x + y + more println(addCurried(1)(2)) //returns 23 } |
Understanding of closures is important to learn the concepts like functors & monads.
Example 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
object ClosuresInScala extends App { def increase(more: Int): Int => Int = x => x + more val closure1: Int => Int = increase(3) // returns a closure Int => Int // and closed over with more = 3 println(closure1(5)) // 5 + 3 = 8, where x = 5, and more = 3 val closure2 = increase(9) // returns a closure Int => Int // and closed over with more = 9 println(closure2(5)) // 5 + 9 = 14, where x = 5, and more = 9 } |
Example 3: REPL
Q. What is the benefit of a closure?
A. As demonstrated below it is pointer to an environment that has data as in the “price” & “discount” Maps to look up for price & discount based on the “productId” and also to the function body, which is the code. This is an analogy to the object oriented programming that has access to data via instance variables & the behaviour via methods.
Calculate product discount amount for a given productid. The “price” & “discount” are free variables defined within the function, but outside the closure – ( productId, price(productId) * discount(productId) / 100).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
scala> def compute = (productId: Int) => { | val price:Map[Int, Double] = Map(1 -> 45.00, 2 -> 67.0, 3 -> 32.0) | val discount:Map[Int, Double] = Map(1 -> 12.00, 2 -> 10.0, 3 -> 5.0) | (productId, price(productId) * discount(productId)/100) | | } compute: Int => (Int, Double) scala> compute(2) res4: (Int, Double) = (2,6.7) scala> compute(1) res5: (Int, Double) = (1,5.4) scala> compute(3) res6: (Int, Double) = (3,1.6) scala> |
Q8. Can you apply the function place holder “_” (i.e. underscore) to the following code snippet?
1 2 3 4 |
val listOfNumbers: List[Int] = (1 to 10).toList val oddNumbers = listOfNumbers.filter(x => x % 2 != 0) // can "_" go here? |
A8. Scala uses the underscore to mean different things in different contexts, but you can think of it as an unnamed wildcard. A place holder makes a functional syntax more concise as shown below.
1 2 3 4 |
val listOfNumbers: List[Int] = (1 to 10).toList val oddNumbers = listOfNumbers.filter(_ % 2 != 0) |
Q9. What will be the output of the following partial function code snippet?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.mytutorial object PartialFunctionInScala extends App { def add(x: Int, y: Int, z: Int) = x +y + z val addAnother = add(1, _:Int, _: Int) println(addAnother) println(addAnother(6, 2)) } |
A9. You can partially apply a function with an underscore, which gives you another function. The “_” here stands for any argument.
Output:
1 2 3 4 |
<function2> 9 |
Q10. Can you write a capitalize function in Scala that takes variable number of Strings input as shown below?
capitalize(“john”, “sam”, “peter”)
capitalize(“apple”, “pears”)
A10. The special syntax with “*” is shown below to take variable length arguments in Scala.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.mytutorial import scala.collection.mutable.ArrayBuffer object VariableArgumentsFunctionInScala extends App { //return type is Seq[String] def capitalize (input: String*): Seq[String] = { input.map (element => element.capitalize) } println(capitalize("john", "sam", "peter")) println(capitalize("apple", "pears")) } |
Output:
1 2 3 4 |
ArrayBuffer(John, Sam, Peter) ArrayBuffer(Apple, Pears) |
Q11. Can you write a recursive function in Scala to calculate the factorial?
A11.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.mytutorial object RecursiveFunctionInScala { //Unit means void function def main (args: Array[String]) : Unit = { println(factorial(4)) } def factorial (input: Int): BigInt = { if(input == 0) { return BigInt(1); } else { return input * factorial(input -1) // recursive call } } } |
First Pass: 4 * factorial(3)
Second Pass: 3 * factorial(2)
Third Pass: 2 * factorial(1)
Fourth Pass: 1 * 1 [Exit Condition is Reached]
The result is: 4 * 3 * 2 * 1 * 1 = 24
Q12. What is tail recursion? Can you write a tail recursion function Scala to calculate the factorial?
A12. A recursive call to be tail recursive, the call back to the function must be the last action performed in that function. In the above example, since the result of each recursive call is being multiplied by the next recursion call, the recursive call is NOT the last action performed in the function, hence NOT a tail recursion.
To make it a tail recursion, the “factorial” function needs to take two arguments. The “interimResult” argument stores the intermediate result, and it is initialized from 1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.mytutorial object RecursiveFunctionInScala { def main(args: Array[String]): Unit = { println(factorial(1, 4)) // 1 is initial intermediateResult value } //tail recursion def factorial(intermediateResult: BigInt, input: Int): BigInt = { if (input == 0) { return intermediateResult; } //recursion is the last call. NO "input * factorial(.....)" factorial(intermediateResult * input, input - 1) } } |
Q. What is the benefit of tail recursion?
A. Since recursion takes place in the stack memory, and in the recursion example the result of each call must be remembered (e.g. 4 * 3 * factorial(2) …), each recursive call requires an entry on the stack until all recursive calls have been made. This makes the recursive call more memory intensive. The “tail recursion” stores the result as an intermediate result.
Q. Since in Scala you can declare a function within another function, is it possible for the factorial method to take a single argument as in factorial(4)?
A. Yes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.mytutorial object RecursiveFunctionInScala { def main(args: Array[String]): Unit = { println(factorial(56)) } def factorial(input: Int): BigInt = { //tail recursion def factorialWithIntermediateResult(intermediateResult: BigInt, input: Int): BigInt = { if (input == 0) { return intermediateResult; } //recursion is the last call. NO "input * factorial(.....)" factorialWithIntermediateResult(intermediateResult * input, input - 1) } factorialWithIntermediateResult(1, input) } } |