Tuesday, 2 January 2018

Introduction to Scala - XVIII

We continue the journey on Scala on the topic of  Partial Functions in this post. For all the work in this post, we will use Read-Evaluate-Print-Loop Scala interpreter. Some familiarity with Java will be of great help in understanding Scala.

To understand the concept of Partial Functions, let us take a List as an example. We have discussed Lists in an earlier post. Let us define a List as shown below:

val List1 = List.range(-3, 3)

Let us compute the square roots of a few elements:

Math.sqrt(List1(0))

Math.sqrt(List1(5))

The results are shown below:

scala> val List1 = List.range(-3,3)
List1: List[Int] = List(-3, -2, -1, 0, 1, 2)

scala> Math.sqrt(List1(0))
res9: Double = NaN

scala> Math.sqrt(List1(5))
res10: Double = 1.4142135623730951


We can also apply the map function as shown below:

List1 map ( x => Math.sqrt(x))

The results are shown below:

scala> List1 map ( x => Math.sqrt(x))
res31: List[Double] = List(NaN, NaN, NaN, 0.0, 1.0, 1.4142135623730951)


We see that some of the value are NaN that we would like to give a miss. So, here comes the Partial Functions that can be applied only to a set of values that we desire.

val root = new PartialFunction[Int, Double] {
  def apply(x: Int) = Math.sqrt(x)
  def isDefinedAt(x: Int) = (x >= 0)
}


The code just means that root takes an integer and returns a double of the square root as defined in the apply function, but, subject to the condition that isDefinedAt evaluates to true. It is more like the program shown below:

if (root.isDefinedAt(1)) root.apply(1)

The results are shown below:

scala> if (root.isDefinedAt(1)) root.apply(1)
res18: AnyVal = 1.0


If we pass any value less than zero, the results are as below:

scala> if (root.isDefinedAt(-1)) root.apply(-1)
res19: AnyVal = () 


Let us see first apply the square root function to our List using map:

scala> List1 map root
res32: List[Double] = List(NaN, NaN, NaN, 0.0, 1.0, 1.4142135623730951)


The above results are the same as that returned by List1 map ( x => Math.sqrt(x)). The difference is when we use collect method as shown below:

scala> List1 collect root
res33: List[Double] = List(0.0, 1.0, 1.4142135623730951)


Now, we see that the conditional defined by isDefinedAt is used and we do not see any NaNs. This functionality of  Partial Functions can be used as a guard to prevent exceptions by filtering out potential elements that may give rise to exceptions. After seeing collect, we can look at collectFirst that returns the first element that satisfies the condition after application of partial function (not obvious here) as shown below:

scala> List1 collectFirst root
res47: Option[Double] = Some(0.0)


This concludes the discussion on Partial Functions in Scala