Commonly used programming constructs in Scala

  • match

‘match’ is similar to ‘switch’ statement which you might be familiar with from other programming languages. ‘match’ however is more powerful owing to the pattern matching capabilities in scala. Following is the syntax for using match

match {

case (value1, condition)=> {block of code to execute if value of variable matches value 1}

case (value2, condition) =>{block of code to execute if value of variable matches value 2}

}

Following is a simple example of using match

val i = 1

i match {
  case 1 => { println("I got 1") } //execute this code if i = 1
  case 2 => { println("I got 2") } //execute this code if i = 1
  //this is the default case if i is neither 1 or 2. 
  // We can store the default value in a variable and use it
  case oops => { println("I didnt get 1 or 2.I got "+oops)} 
}

I got 1

val i = 3

I didnt get 1 or 2. I got 3

In addition to matching values, you could use match to check patterns or data type

def matchType(a:Any) {

  a match {
    case x: Int => {
      println("I got an Int")
    }
    case c: Char => {
      println("I got a Char")
    }
    case _ => {
      println("I got nothing")
    }
  }
}

val c = 'c'
matchType(c)
I got a Char
res0: Unit = ()

val i = 3
matchType(i)
I got an Int
res1: Unit = ()

val s = "hello"
matchType(s)

I got nothing
res2: Unit = ()

It is worth mentioning that when matching say X with Y, Y has to be of same type or subtype of X. If this condition is not satisfied then the code will not compile. It is obvious because if you want to match say a Car with some other items, then the other items should be cars. It doesn’t make sense to match a car with a Ship or a Place as they’ll never match. Going by same logic, Scala compiler will check if the items being matched could be matchable. The above code worked because Int and Char are subtypes of Any. An object of type Any could be of any type and thus compiler allowed matching Any with Int or Char. However following would not compile as an object of type Double cannot match with type Int (note that we are not talking Double taking Int values, we are comparing types)

def matchType(a:Double) {

  a match {
//this will not compile
    case p: Int => {
      println("I got a Double")
    }
    case _ => {
      println("I got nothing")
    }
  }

}

This will compile because a Double can take an Int value

def matchValue(d:Double) {

  d match {
//we have specified 3 (an Int). Compiler would probably convert it to 3.0 internally
    case 3 => {
      println("I got a Double")
    }
    case _ => {
      println("I got nothing")
    }
  }
}

matchValue(3.0)
I got a Double
res1: Unit = ()

This will not compile because an Int cannot take a Double value

def matchValue(d:Int) {

  d match {
// this line will not compile
    case 3.0 => {
      println("I got a Double")
    }
    case _ => {
      println("I got nothing")
    }
  }

}

In addition to comparing values and types, another common use of match is to traverse a list. A list is a data structure similar to a LinkedList. A list (1,2,3,4,5) could be visualised as a combination of elements, linked together in sequence i.e. 1 is linked to 2, 2 is linked to 3, 3 is linked to 4 and so on. The end of the list is represented by Nil. Thus 5 is linked to Nil. In code, we represent this linking using Cons (::) symbol. Using Cons pattern, we can identify if a list has elements and access each element till the complete list has been traversed.

//example to demonstrate use of Cons to make a list
//common way to create a list
val l1 = List(1,2,3,4,5)
l1: List[Int] = List(1, 2, 3, 4, 5)

//using Cons (::) to make a list
val l2:List[Int] = 1::2::3::4::6::Nil
l2: List[Int] = List(1, 2, 3, 4, 6)

//
def isEmpty[A](l:List[A]): Unit = {
 l match {
 case Nil => println("Empty")
//using the Cons pattern to see if list is empty or not. If a list can be 
//broken into 'element Cons something' then the list is not empty
 case x::xs => println("not empty")
 }
}

val l3 = List(1,2,3,4,5,6,7)
val l4 = List()

isEmpty(l3)
not empty

isEmpty(l4)
Empty

//another example to traversing the list. This time, we count the elements of
// the list using recurssion. Also note the use of functional programming as we
//avoid using vars.
//explanation of the two cases
//case Nil - we have reached Nil. Nil denotes that there are 0 elements in the list now
//case x::xs - if the list has elements, then we know it has at least 1 element right now.
//check if there are more elements in the list using recurssion (calling listSize
//again with remaining list elements (xs). Add 1 as there is atleast 1 element 
//right now

def listSize[A](l:List[A]): Int = { 
 l match {
 case Nil => 0
 case x::xs => {(listSize(xs)+1)} 
 }
}

listSize(l3)
res2: Int = 7

listSize(l4)
res3: Int = 0

Another useful feature of ‘match’ is the ability to use a case only for certain criteria. These restrictions are called guards. They are provided by using ‘if’ statements after the case. Say in above example, say we want to find the number of odd and even elements in the list. We could do by specifying multiple cases of the same pattern but a pattern will match only one case due to guards

def oddOrEven(l:List[Int]): Unit = {
  l match {
    case Nil => println("Empty")
    case x::xs if x % 2 == 0 => {oddOrEven(xs); println("Even")} 
    case x::xs if x % 2 != 0 => {oddOrEven(xs); println("Odd")}
  }
}

val l1 = List(1,2,3,4,5)
oddOrEven(l1)
Empty
Odd
Even
Odd
Even
Odd

If is worth mentioning that

case x::xs if x % 2 == 0 => {oddOrEven(xs); println("Even")}

is different from

case x::xs => {if (x % 2 == 0) {oddOrEven(xs); println("Even")}}

In 1st case, the case is not matched  and thus the expression after => is not executed. In 2nd case, the case is matched and the expression after => is executed. We do not see the result because the if condition in the expression is not true. Here is another version of odd0rEven example. In this one, we count the occurrences of odd and even numbers and return the counts as a Tuple2 (the first element of Tuple2 represents count of odd occurrences and the second element of Tuple2 represents count of even occurrences)

val l1 = List(1,2,3,4,5,6,7)

def oddOrEven(l:List[Int]): (Int,Int) = {
  l match {
    case Nil => (0,0)
    //return (0,1) for even.
    //we know this element is even. So we need to add 1 to 2nd member of Tuple2
    case x::xs if x % 2 == 0 => {val t = oddOrEven(xs); (t._1,t._2+1)}
    //return (1,0) for Odd
    case x::xs if x % 2 != 0 => {val t = oddOrEven(xs);(t._1+1,t._2)}
  }
}
oddOrEven: oddOrEven[](val l: List[Int]) => (Int, Int)

oddOrEven(l1)
res0: (Int, Int) = (4,3)
  • apply

We generally use objects in following notation

objectname.method

In Scala, you could use an object like a function call as follows:

objectname(optional arguments)

When scala compiler sees such invocation, it looks for ‘apply’ method of similar syntax in the class or its companion object (discussed below) and executes the body of the ‘apply’ method.  In other words objectname(optional arugments) is same as objectname.apply(optional arguments).

Consider following example. MyIncr is a class which increments some initial value by a specific amount.

class MyIncr() {
  def apply(initValue:Int, incr:Int) = {println(initValue+incr)}
  def apply(initValue:Int) = {println(initValue+1)}
}

As we have defined ‘apply’ methods, we can use the convenient format of calling the object as a function instead of using object.method format.

//this creates an object. It doesnt call apply
val ma = new MyIncr()
ma: MyIncr = MyIncr@144604a3

//this is same as ma.apply(4,4)
ma(4,4)
8
res2: Unit = ()

//this is same as ma.apply(4)
ma(4)
5
res3: Unit = ()

The above example also demonstrates that a class can have multiple versions of apply method.

apply and constructors

Note that ‘apply’ is different from constructor. They are not related. In previus example, the command ‘ma=new MyIncr()’ is used the constructor but doesn’t call apply method. The use of keyword new invokes the constructor. Apply is simply a mechanism for us to use an object using a function call notation (using brackets()). What we want to do in apply function is entirely up to us. We could create new objects, process arguments and do anything else.

For example, in List, apply is implemented to return the value from the list. It takes an integer as an argument. The integer specifies which location to go to in the list to fetch the value. In a Set, the apply is used to check if a value exists in the set. The value we want to test for is specified as an argument in the apply method

val l = List(1,2,3) //apply is not called at this time. 
l(1) //apply is called now
res4: Int = 2

val s= Set(1,2,3) //apply is not called at this time
s(1) //apply is called now
res5: Boolean = true

‘apply’ gives us more flexibility as we are not limited to returning the object of the class. Using ‘apply’ to create objects is covered later in this blog under companion object section.

Hint to remember apply – if we use object(arguments), we are executing object.apply(arguments)

  • Option – Some/None

Programming languages like C, C++ and Java use the word ‘null’ to represent a non-existing value. In Scala, you use Option. The purpose of using Option is to eliminate null value check from your code. Option is used when there could be some value or nothing. Accordingly, an Option can have two values, Some or None. ‘Some’ means that a value exists, None is similar to ‘null’ and means that there is no value. As Option can have zero or one value, an Option can be considered as a collection of zero or one elements. You could use ‘match’ or getOrElse or forEach to access elements in case Option is not equal to None. Consider the following example to understand how to use Option to return a Some or None value.

Say you want to write a function which takes 3 integers and finds whether any of the integers is an even number. The function returns the first even number it finds. Such a function could either return the even number (if found) or nothing. Thus it makes sense to use Option here.

def isEven(i:Int, j:Int, k:Int): Option[Int] = {
  if (i%2 == 0) Some(i) else {
    if (j % 2 == 0) Some(j) else {
      if (k % 2 == 0) Some(k) else None
    }
  }
}

val p = isEven(1,2,3)
p: Option[Int] = Some(2)

val q = isEven(1,3,5)
q: Option[Int] = None

Here is another example. In this, instead of returning the first even number, we return all the even numbers stored in a List. We could have used List as return value but as the list could be empty, it is better to use Option as it indicates that the value could be something or nothing

def isEvenList(i:Int, j:Int, k:Int): Option[List[Int]] = {
  val l = List(i,j,k)
  val newl = l.filter(x=>x%2 == 0)
  if (newl.isEmpty) None else Some(newl)

}

val p1 = isEvenList(1,2,3)
p1: Option[List[Int]] = Some(List(2))

val q1 = isEvenList(1,3,5)
q1: Option[List[Int]] = None

//examples to show how to access values from Option
//examples of match
p1 match {
  case Some(l:List[Int]) => println("Found list of size "+l.size)
  case None => println("no int")
}
Found list of size 1
res2: Unit = ()

q1 match {
  case Some(l:List[Int]) => println("Found list of size "+l.size)
  case None => println("no list")
}
no list
res3: Unit = ()

//example of getOrElse. Use getOrElseto assign a default value if None is returned

//if p2 is None, make p2 an empty list instead of None
val p2 = isEvenList(2,3,4).getOrElse(List())
p2: List[Int] = List(2, 4)

//if q2 is None, make q2 an empty list instead of None
val q2 = isEvenList(5,7,9).getOrElse(List())
q2: List[Int] = List()

//q3 will be assigned value of 1 here. Note that as Option is of type List[Int] 
//and 1 is an Int, the compiler picks their common baseclass Any to decide the 
//type of q3
val q3 = isEvenList(1,3,5).getOrElse(1)
q3: Any = 1

// iterate through q4. is q4 or q5 are None, nothing will be printed (as the 
//colletion is empty). Else the only element (returned by Some) will be printed

val q4 = isEvenList(1,2,4)
q4.foreach(println _)

List(2, 4)

val q5 = isEvenList(1,3,5).foreach(println _)
//nothing gets printed
  • Singleton and Companion objects

Singleton

We generally create methods inside a class as follows:

class MyClass {
  def sum (x:Int,y:Int) = x+y
}

val mc = new MyClass
mc.sum(1,2)
Int = 3

If we want to use the method ‘sum’, we will need an instance (object) of MyClass. As the method ‘sum’ is generic, it would be better not to associate it with a specific class. To do this, we could instead create a Singleton which would allow us to use the method ‘sum’ globally without a need to associate it with any class. To create a Singleton, simpley replace the keyword class with keyword object. See example below:

object MyClass {
  def sum (x:Int,y:Int) = x+y
}
//use sum by fully specifying its location/package
MyClass.sum(1,2)
Int = 3

//Alternative way to use a global function by importing the corresponding package
import MyClass.sum
sum(1,2)
Int = 3

Singleton provides a convenient mechanism to define global constants or functions (also known as static variables or functions) which are not associated with particular class or object. Singleton can be used to create utility functions.. As an example, you could create a singleton to store mathematical constants and functions and use them globally in our program

//say this is defined in a file named MathConstants.scala
object MathConstants { //object means this is a singleton 
 import Math._
 val MYPI = 3.14159
 def pythagoras(x:Int, y:Int) = Math.sqrt(x*x + y*y)
}

//say this is defined in a file named MathExample.scala
import MathConstants._
object MathExample extends App {
 println("my PI is"+ MathConstants.MYPI)
 println(" Hypotenuse of 3 and 4 is ",MathConstants.pythagoras(3,4))
}

Using Companion Object to create class specific static members

Another commonly used concept is Scala is Companion Object. It is also constructed in the same was as a singleton (using object keyword). The difference is that a companion object is a ‘companion’ of an existing class. Just like a Singleton is used to create static functions or variables, a companion object is used to create static functions for a class. A static function or variable ,by definition, is not associated with individual objects of a class. Thus, if you have some methods or functions which are specific to a class but not to individual instances of that class then you can create them in a companion object. To create a Companion object, simply use keyword object and give it the same name as the name of the class to which is belongs. For example, say we want to keep a count of number of instances of a particular class created. As this count is a class specific variable, we should create it in a companion object

class C {
  C.counter+=1
  def someCFunction = {println ("some C function. Counter is "+C.counter)}
}

object C {
  var counter:Int=0
}

val c1 = new C
c1.someCFunction
some C function. Counter is 1


val c2 = new C
c2.someCFunction
some C function. Counter is 2

In above example, the counter which tracks how many instances of a class have been created increases everytime we create an object of type C. We cannot keep this counter in the class itself because then each object will get its own copy of the counter and will be initialised and incremented only one time. See following code:

class C {
  var counter:Int=0 //using local copy in the class. This will not work
  counter+=1
  def someCFunction = {println ("some C function. Counter is "+counter)}
}

val c1 = new C
c1.someCFunction
some C function. Counter is 1
val c2 = new C
c2.someCFunction
some C function. Counter is 1

Using Companion Object to create object factory

Another useful feature of companion objects is to create Factory methods. A factory methods lets you define how an object should be created. Say we want to create a class which could give us different types of collections. We just need to specify which type we want. In the following example, we create a class which could return to us a List or an Array. In case we specify a wrong argument, the class throws an exception.

class CollectionFactoryStyle1 [A] (s:String){
  println("CFS1 constructor called")

  val collectionType:String = s match {
    case "list" => "list"
    case "array" => "array"
//throw exception is argument is incorrect
    case _ => throw new IllegalArgumentException("mention list or array")
  }

  def giveCollection[A:reflect.ClassTag]:Traversable[A] = { collectionType match {
    case "list" => {
      List[A]()
    }
    //classTag is required else the code for array will not compile
    case "array" => {
      new Array[A](1)
    }
  }
}
}

val cl1 = new CollectionFactoryStyle1[Int]("list")
CFS1 constructor called
cl1: CollectionFactoryStyle1[Int] = CollectionFactoryStyle1@4ca4a4ea

cl1.giveCollection
res0: Traversable[Nothing] = List()

val ca1 = new CollectionFactoryStyle1[String]("array")
CFS1 constructor called
ca1: CollectionFactoryStyle1[String] = CollectionFactoryStyle1@43a3eee6

ca1.giveCollection
res1: Traversable[Nothing] = WrappedArray(null)

//we use try/catch to handle the exception
try {
  val cs1 = new CollectionFactoryStyle1[Boolean]("")
  cs1.giveCollection
} catch {
  case exp: IllegalArgumentException => {
    println(exp)
  }
}

To use above class, we use try/catch block to catch the exception. Also we get our collection in two steps, we first create an object and then we call giveCollection to get the collection. Both these increases our code size and makes our code less readable.

An alternative way is to use companion object and handle incorrect parameters more appropriately.

class CollectionFactoryStyle2 [A](s:String){
  println("CFS2 constructor called")
}

object CollectionFactoryStyle2 {
  def apply[A:reflect.ClassTag](s: String): Option[Traversable[A]] = {
    println("apply called")
    s match {
      case "list" => {
        Some(List[A]())
      }
      //classTag is required else the code for array will not compile
      case "array" => {
        Some(new Array[A](1))
      }
      case _ => None
      }
    }
  }

//When we use 'new', constructor is called. We do not get our list. 
val cl = new CollectionFactoryStyle2[Int]("list")
CFS2 constructor called
cl: CollectionFactoryStyle2[Int] = CollectionFactoryStyle2@7f94b3f7

//to get our collection, we cannot use new anymore. We have to use function notation
val cl2 = CollectionFactoryStyle2[Int]("list")
apply called
cl2: Option[Traversable[Int]] = Some(List())

val ca = CollectionFactoryStyle2[Int]("array")
apply called
ca: Option[Traversable[Int]] = Some(WrappedArray(0))

CollectionFactoryStyle2[String]("")
apply called
res3: Option[Traversable[String]] = None

In above example,  we get two advantages. Getting our collection reduces to a single statement because we are using ‘apply’ (ignore the comment about new for now). Also, we are able to handle incorrect arguments more elegantly. The user could simply check for Some or None instead of complex try/catch blocks.

You might think of calling ‘apply’ from constructor to get the collection. It will not work because a constructor can only return an object of the same class and not of any other class.

class CollectionFactoryStyle2 [A](s:String){
  println("CFS2 constructor called")
  CollectionFactoryStyle2.apply(s)
}

object CollectionFactoryStyle2 {
  def apply[A:reflect.ClassTag](s: String): Option[Traversable[A]] = {
    println("apply called")
    s match {
      case "list" => {
        Some(List[A]())
      }
      //classTag is required else the code for array will not compile
      case "array" => {
        Some(new Array[A](1))
      }
      case _ => None
      }
    }
  }


val cl = new CollectionFactoryStyle2[Int]("list")
CFS2 constructor called
apply called
cl: CollectionFactoryStyle2[Int] = CollectionFactoryStyle2@40936956

In example above, even though ‘apply’ was called, we ‘cl’ is of type CollectionFactoryStyle2, not List

There is one issue with above implementation. When we used ‘new’, we didn’t get the list. Basically, with ‘new’ we are creating an object of type CollectionFactoryStyle2 and with apply, we are getting back our collection. This is confusing. What we need to do is create a uniform approach for creating objects we want to create. To achieve this, we can make the constructor private which will not allow the creation of CollectionFactoryStyle2 using new.

class CollectionFactoryStyle2 [A] private (s:String){
  println("CFS2 constructor called")
  CollectionFactoryStyle2.apply(s)
}

//this will not work now. Thus we have limited creation of objects by only using apply
val cl = new CollectionFactoryStyle2[Int]("list")

Using Companion Object to define implicit

Another use of companion object is in defining implicits. While looking for implicit definitions, the compiler looks in local scope as well as in companion objects of the required type. Thus you could declare implicit values and conversions in the companion object. This would provide a convenient way to manage implicits. Say we have a class X and a function which prints something about X. The function takes implicit argument of X. You could declare an implicit value of X either in local scope or in a companion object of X (the implicit type required by function)

class X (s:String){
  val s1 = s
}

//we could declare an implicit value of X here
implicit val ox = new X("outside object")

object X  {
// we could declare implicit value of X here inside companion object
  implicit val ix = new X("inside object")

//this second value will create ambuiguity error. Just want to show that 
//you cannot have multiple implicits in same scope

 // implicit val ix2 = new X("inside object2")
}

def xToString(implicit x:X): Unit ={
  println (x.s1)
}

//provide X explicitly
xToString(new X("first"))
first
res2: Unit = ()

//Compiler wll look for implicit now. It will check local scope first and thus
// will print ox
xToString
outside object
res3: Unit = ()

//if you comment ox declaration, compiler will print ix as after checking 
//local scope, compiler looks in companion objects for implicit definitions

//implicit val ox = new X("outside object")
xToString
inside object
res3: Unit = ()

  • type

Type helps us give alternative names or alias to data types. In oddOrEven example, we used the first element of Tuple2 to count occurrences of odd numbers and the second element of Tuple2 for count of even occurrences. We could give more meaningful name to the Tuple by using Type

type odd = Int
defined type alias odd

type even = Int
defined type alias even

val l1 = List(1,2,3,4,5,6,7)

def oddOrEven(l:List[Int]): (odd,even) = {
  l match {
    case Nil => (0,0)
    //return (0,1) for even.
    //we know this element is even. So we need to add 1 to 2nd member of Tuple2
    case x::xs if x % 2 == 0 => {val t = oddOrEven(xs); (t._1,t._2+1)}
    //return (1,0) for Odd
    case x::xs if x % 2 != 0 => {val t = oddOrEven(xs);(t._1+1,t._2)}
  }
}

oddOrEven(l1)

In above example, we gave data type Int new aliases called odd and even. Now we can use odd or even in places where we would use Int. Type help us give meaningful names to types which make improves code readability like in the example above where it is clear now that the first element is for odd numbers while the second is for even numbers

  • Range

Range creates set of values from some start value to some end value. The start value is always included in the Range. The set of values created are ‘incremented’, starting from the start value. Depending on how the Range is created (using to or until methods), the end value may or may not be included in the range. The ‘increment’ could be moving to next number or next alphabet charactet, depending on the type of elements in the range. An increment step could also be specified usiung ‘by’ method.

//create a range of integers from 1 to 5. Default step size is 1
val ri = (1 to 5)
ri: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

//create range of alphabets from a to e
val rc = ('a' to 'e')
rc: scala.collection.immutable.NumericRange.Inclusive[Char] = NumericRange(a, b, c, d, e)

//create set of integers. step by 2. So we have only odd integers 
val ro = (1 to 5 by 2)
ro: scala.collection.immutable.Range = Range(1, 3, 5)

//do not include end value
val ri2 = (1 until 5)
ri2: scala.collection.immutable.Range = Range(1, 2, 3, 4)
  • Tuple

A Tuple is a data structure which can hold together multiple other data types at the same time. Size of a Tuple is the number of data types it consists of. If your programming background is in language like C, a Tuple can be considered similar to ‘struct’ in C language.

A regular variable consists of a single data type and a value. For example, in the following statement, we declare two separate variables of type Integer and String. Logically, they represent name and age and thus can be considered as belonging to some single entity (say some individual person) but physically, they are separate variables. It would be programmers responsibility to use them together whereever required.

val name:String = "Andy"
val age:Int= 10
println(name+" age is "+age)

Using a Tuple, we could group these two variables into a single variable.

val person:Tuple2[String,Int] = ("Andy",10)
println(person._1+" age is "+person._2)

As the tuple will hold two variables, we use Tuple2. We also specify that the 1st element of the tuple would be a String and the second would be an Int. We access individual elements using _1 and _2 methods of class Tuple.

We discussed a Tuple of size 2. In general, size of a Tuple can go from 1 till 22 elements. Following are some ways by which you can declare tuples of different sizes.

val t1 = Tuple1(1)
t1: (Int,) = (1)

val t2 = Tuple2(1,2)
t2: (Int, Int) = (1,2)

val t6 = (1,'C',3,4,5, "andy")
t6: (Int, Char, Int, Int, Int, String) = (1,C,3,4,5,andy)

One advantage of using a Tuple is that it allows us to pass a group of variables together to and from a function. Thus, you can return multiple values from a function using a tuple. In the following example, we count the number of odd and even elements in a Range.

type odd =  Int
type even = Int
def countOddEven(r:Range,oet:Tuple2[odd,even]):Tuple2[Int,Int] = {
  if(r.length == 0 ) oet else {
    if(r.head % 2 == 0) countOddEven (r.tail, (oet._1,oet._2+1)) else {
      countOddEven(r.tail,(oet._1 +1, oet._2))
    }
  }
}
val result= countOddEven(r,(0,0))
result: (Int, Int) = (6,5)

productIterator method can be used to iterate over elements of a Tuple

val t22 = Tuple22(1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2)
val it = t22.productIterator
it.foreach(println _)
  • for, guard, yield

‘for’ is the construct for looping. If you have some programming background, there is a high probability that you have used for loops. In summary, if you want to do something repeatedly, you can use ‘for’ loop

Say we want to print first 10 integers. We could do this using for loops as follows:The ‘for’ loop is written as

for (i <-(1 to 10)){println(i)}
1
2
3
4
5
6
7
8
9
10
res1: Unit = ()

In above code, ‘for’ is a keyword, (1 to 10) is a Range data type (with default step size as 1) which decides for how many times the for loop will be executed. In above code, the ‘for’ loop will be executed till the values from Range goes from 1 to 10 with step size of 1 (so 10 times). Current value from the range is assigned to ‘i’ (which is a ‘val’ variable. val/var are discussed here). The ‘<-‘ operator is called a generator as it is ‘generating’ values from Range. Visually, it looks as if the generator is picking values from Range and assigning those individual values to ‘i’. Here is another example. In this example, even though ‘l’ is a list, (0 to l.length-1) is a Range.

val l = List("a","b","c")
for (i<- (0 to l.length-1)) {println(l(i))}
a
b
c
res1: Unit = ()

As Range (a,b) would give all values including ‘a’ and ‘b’, we restricted the Range to generate values till length-1 as valid indices of our list l (of size length) starts from index 0 and goes up to length -1. Another way to achieve the same result could be to use ‘unti’ instead of ‘to’. ‘until’ does not include the value ‘b’ in Range (a,b).

for (i<- (0 until l.length)) {println(l(i))}
a
b
c
res2: Unit = ()

To demonstrate that ‘i’ is a val and not a ‘var’, try the following code. It will not compile because the compiler would complain about reassignment to a variable of type ‘val’.

for (i<- (0 to l.length-1)) {i=i+1; println(l(i))}
Error:(9, 32) reassignment to val
for (i<- (0 to l.length-1)) {i=i+1; println(l(i))}
 ^

We can also use multiple Range collections. The following uses two Range collections. For each value of 1st Range (the Range on left), all the values of 2nd range (the Range on right) are computed and processed.

for (i<-(2 to 3); j <-(1 to 10)) {println("i is "+i+", j is "+j+", i*j is :"+(i*j))}

i is 2, j is 1, i*j is :2
i is 2, j is 2, i*j is :4
i is 2, j is 3, i*j is :6
i is 2, j is 4, i*j is :8
i is 2, j is 5, i*j is :10
i is 2, j is 6, i*j is :12
i is 2, j is 7, i*j is :14
i is 2, j is 8, i*j is :16
i is 2, j is 9, i*j is :18
i is 2, j is 10, i*j is :20
i is 3, j is 1, i*j is :3
i is 3, j is 2, i*j is :6
i is 3, j is 3, i*j is :9
i is 3, j is 4, i*j is :12
i is 3, j is 5, i*j is :15
i is 3, j is 6, i*j is :18
i is 3, j is 7, i*j is :21
i is 3, j is 8, i*j is :24
i is 3, j is 9, i*j is :27
i is 3, j is 10, i*j is :30

We used Range in above examples. We could used any other collection as well in ‘for’ loop. Consider following examples in which we use a List, Map and Array

val l = List("a","b","c")
val a = Array('1','2','3','4')
val r = (1 to 3)
val m = Map(1->true, 2->false)

for (i <- r){println(i)}
1
2
3
res0: Unit = ()

for (i<- l) println(i)
a
b
c
res1: Unit = ()

for (i <- a) println(i)
1
2
3
4
res2: Unit = ()

for(i<-m) println(i)
(1,true)
(2,false)
res3: Unit = ()

The generator, <-, calls an iterator on the collection and returns the values in the collection, assigning the value to ‘i’. Thus, the data type of ‘i’ would be same as type of elements contained in the collection. The iterator is discussed in blog on collections and List

By default, all the values assigned by the generator to the val variable, i, are processed. You could specify conditions such that only those values from the collection, which satisfy a predicate are used. This filtering of elements is called guard. It is used on other constructs as well like match and case.

val r = (1 to 10)

//process only even values
for (i<- r; if i%2 == 0) println(i)
2
4
6
8
10
res0: Unit = ()

We discussed that in for loop, elements are ‘extracted’ from a collection, c1, by generator, <-, and assigned to a val, i. Using ‘yield’, you can ‘collect’ these values. ‘yield’ means produce. Thus,’yield’ produces a new collection which in simple terms is a data structure which stores the values produced. The type of the new collection would be dependent on the type of collection, c1, used in the ‘for’ loop and the type of processing done on ‘i’. The values in the new collection would depend on the processing we do on ‘i’. See the following example

val l = List("a","b","c")
val a = Array('p','q','r','s')
val r = (1 to 3)
val m = Map(1->true, 2->false)
val s = Set(9,8,7)

// 'r' contains integers. so 'i' would be an integer. i*2 will double it. 
//Result is stored in a Vector
for (i <- r)yield i*2
res5: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6)

// 'l' contains String. so 'i' would be an String. i+"Z" would be string concatenation 
//Result is stored in a String
for (i<- l) yield (i+"Z")
res6: List[String] = List(aZ, bZ, cZ)

// 'a' contains Char. so 'i' would be a Char. i+2.toChar would give a new character
// which is 2 places after this one in ASCII table
for (i <- a) yield (i+2).toChar
res7: Array[Char] = Array(r, s, t, 6)

//'m' is Map of [Int,Boolean] which of type Tuple2. In yield, we extract the
//first element of Tuple2 using _1 method. These are stored in a List
for(i<-m) yield i._1
res8: scala.collection.immutable.Iterable[Int] = List(1, 2)

// 's' contains Int. so 'i' would be a Int. i*2 in integer multiplication
for(i<-s) yield i*2
res9: scala.collection.immutable.Set[Int] = Set(18, 16, 14)
  • Class

A class is defined in Scala using class keyword. A class can take arguments. Following examples create two classes and then instantiate objects of these classes using ‘new’ keyword

//a class without argument
class A1 {

}
//a class with argument
class A2 (v:String){

}

val a1= new A1
val a2 = new A2("Andy")

A class generally consists of variables and methods which process those variables. We can modify above example to include these two things

class A2 (fn:String, val ln:String){
 //all statements which are not methods or auxiliary constructors will form part of primary constructor
  val name = fn
  def getName:String = name
  def printName = println(fn)
  val lastName = ln
  println("primary constructor, fn:" + name +", ln: "+lastName)
  
  def this() {
//this is call to primary constructor created automatically by Scala
    this("defaultName", "defaultLastName")
    println("aux constructor, no incoming arguments. fn:" + name +", ln: "+lastName)
  }
  def this(fn:String) {
    this(fn,"defaultLastName")
    println("aux constructor, only fn argument. fn:" + name +", ln: "+lastName)
  }
}

//create object. primary constructor will be called here
val a2 = new A2("Andy","Cale")
primary constructor, both fn and ln fn:Andy, ln: Cale
a2: A2 = A2@61188f5b

//call methods on objects
println(a2.getName)
Andy
res0: Unit = ()

a2.printName
Andy
res1: Unit = ()

//access class variables directly
println(a2.lastName)
Cale
res2: Unit = ()

//this will not compile because fn declaration doesnt' contain val or var
//a2.fn 
//this will compile because ln declaration has val (could be var too)
a2.ln
res3: String = Cale

//auxilary constructors will be called here
val a3 = new A2
primary constructor, both fn and ln fn:defaultName, ln: defaultLastName
aux constructor, no incoming arguments. fn:defaultName, ln: defaultLastName
a3: A2 = A2@1647c191

val a4 = new A2("Mark")
primary constructor, both fn and ln fn:Mark, ln: defaultLastName
aux constructor, only fn argument. fn:Mark, ln: defaultLastName
a4: A2 = A2@70e45fd7

The above example demonstrates come interesting things about classes in Scala.

  • We can pass arguments to a class. If the argument is declared as val (or var) then we can access it from its objects (eg a2.v1) else we cannot (eg a2.v1)
  • There is no primary constructor for classes in scala. All statements inside a class definition which are not a method (def) are executed at time of object creation in the same order in which they are written in the class. In other words, Scala compiler would create a primary constructor for us by picking all statements outside methods. In example above, a primary constructor which takes two arguments of type String is created. That is why, the assignment of ‘name’, ‘lastname’ and the print statements get executed at time of object creation (in order in which they are written in the code). It doesn’t matter that these were scattered inside the class definition (lastName and print are written after method declarations)
  • We can declare auxiliary constructors. It is important however that some previously defined constructor (primary or auxiliary) gets called as the first statement of an auxiliary constructor. A constructor is called using ‘this’ keyword

The arguments passed to a constructor can take default value

class A2 (fn:String, val ln:String = "someLastName"){

  val name = fn
  def getName:String = name
  def printName = println(fn)
  val lastName = ln
  println("primary constructor, fn:" + name +", ln: "+lastName)
}

val a4 = new A2("Mark")
primary constructor, fn:Mark, ln: someLastName
a4: A2 = A2@30bebefa

  • case class

If we prefix keyword ‘case’ in class, we create a case class. Creating a case class provides benefits that Scala compiler generates a lot of code for you. In the example below, if we print object a2, we get an output which isn’t very helpful. Also, another object a3 (with the same content as a2) isn’t treated as equal to a2.

class A2 (fn:String, val ln:String){

  val name = fn
  def getName:String = name
  def printName = println(fn)
  val lastName = ln
}

val a2 = new A2("Andy","Cale")
println(a2)
A$A209$A$A209$A2@32b6450c
res0: Unit = ()

val a3 = new A2("Andy","Cale")
a2 == a3
res1: Boolean = false

If we convert the above class into a case class, the compiler will generate code for us which removes a lot of boiler plate code.

  • You do not need ‘new’ when creating an object
  • Compiler creates toString, equal method for us. Objects of case classes are compared structurally.
  • We can also access all arguments passed to  the constructor even if they are not prefixed with val or var. This is because compiler would convert a variable with no prefix into a val
case class A2 (fn:String, val ln:String){

  val name = fn
  def getName:String = name
  def printName = println(fn)
  val lastName = ln

}

//we do not need 'new' with case classes
val a2 = A2("Andy","Cale")
println(a2)
A2(Andy,Cale)
res0: Unit = ()

val a3 = A2("Andy","Cale")

println(a2)
a2 == a3
res1: Boolean = true

println("fn: "+a2.fn+", ln:"+a2.ln)
fn: Andy, ln:Cale
res2: Unit = ()

Another interesting feature of case class is pattern matching. As the name suggests, an interesting feature of scala is you can look for patterns in variables. We can use pattern matching in case classes. In the following code, we check (for a pattern) if the last name is “Cale”.

class A2 (var fn:String, val ln:String){
  val name = fn
  def getName:String = name
  def printName = println(fn)
  val lastName = ln

}

val a2 = A2("Andy","Cale")
val a3 = A2("Andy","cale")
val a4 = A2("Andy","pale")

def isCale(a2:A2) {
  a2 match {
    case A2(_, ln) if (ln.equalsIgnoreCase("cale")) => println("lastname Cale")
    case _ => println("not Cale")
  }
}
isCale(a2)
lastname Cale
res0: Unit = ()

isCale(a3)
lastname Cale
res1: Unit = ()

isCale(a4)
not Cale
res2: Unit = ()
  • sealed and final class/trait

A final class/trait cannot be extended.

class A {
  
}

final class B extends A {
  
}

class C extends A {
  
}

class D extends B{
  
}

If we had declared class B as ‘sealed’ instead of ‘final’,  it could have been extend but only by classes defined in same source file in which class B is defined.

  • TypeTag, ClassTag, Manifest

In Scala, we use type parameters to create generic functions. For example, following function finds whether elements of a list are of some type T (the code has errors though!).

def checkListType[T](x:List[Any]): Boolean= {
  x.head match {
    case e:T => true //check for pattern that head is of type T
    case _ => false
  }
}

checkListType[Int](List(1)) //true
checkListType[Int](List("1")) //true. ERROR!

As you note above, the function returns true even if the list contains String. This issue is called Type Erasure. It is a problem which occurs when the type information gets lost at runtime. ClassTag, TypeTag and Manifest (now deprecated) are used to solve the problem of type erasure.

To understand the issue, at compile time, we write the code which matches the type of head of the list with some T (which is also passed when we call the function, eg. checkListType[Int]). However, at runtime, the scala compiler doesn’t pass this information to JVM. In other words, the code gets compiled to something like follows where the T (Int) gets changed to something very generic like type Object which is a superclass of all types. As x.head (irrespective of whether it is an Int or a String) is also of type Object (because Object is superclass of everything), the code always returns true.

//this code is for illustration only to demonstrate that T gets changed to 
//something more generic at compile time.
def checkListType[T](x:List[Any]): Boolean= {
  x.head match {
    case e:Object => true
    case _ => false
  }
}

To solve the problem, simply add ClassTag to above code

def checkListType[T:ClassTag](x:List[Any]): Boolean= {
  x.head match {
    case e:T => true
    case _ => false
  }
}

checkListType[Int](List(1)) //true
checkListType[Int](List("1")  //false

Following is another version of the function. Here ‘x’ could be of any type, not necessarily a list.

def checkListType[T:ClassTag](x:Any): Boolean= {
  x match {
    case e:T => true
    case _ => false
  }
}

checkListType[Int]](1) //true
checkListType[String]("1")//true
checkListType[String](1)//false

The names of the classes, ClassTag, TypeTag, give you some hint about what they do. They add ‘tags’ to generic types and pass them to runtime JVM. So an Int gets tagged as  scala.reflect.ClassTag[Int] = Int and a String gets tagged as scala.reflect.ClassTag[String] = java.lang.String. To find tags of other types, simply use classTag method of ClassTag class

classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

classTag[String]
res1: scala.reflect.ClassTag[String] = java.lang.String

//alternative way. Provide classTag directly instead of using implicit value from Compiler
checkListType[String]("1")(classTag[String]) //trye
checkListType[String](1)(classTag[String]) //false 
checkListType[Int](1)(classTag[Int]) //true

classTag[List[Int]]
res2: scala.reflect.ClassTag[List[Int]] = scala.collection.immutable.List

classTag[List[String]]
res3: scala.reflect.ClassTag[List[String]] = scala.collection.immutable.List

The last two statements above tell us that tags for List[Int] and List[String] are same! This brings us to the problem with Classtags. ClassTag can identify whether a type is a List but it cannot distinguish whether the List holds Integers and String. This happens because ClassTag doesn’t carry enough information for runtime JVM and thus is unable to distinguish beyond ‘first level’ of data type. We can prove this by running our example above with types specified as List[Int] and List[String]

checkListType[List[Int]](List(1)) //true
checkListType[List[String]](List(1)) //true

As we notice above, the runtime tells us that lists contain Integer while we specified String in the second example!

In general, all information about a type beyond the first level is lost in ClassTag. So, using pattern matching, we can find that we have a list or an Int or a String but cannot tell whether the list is of Int or String.

To distinguish beyond ‘first level’, we will need to use TypeTag. TypeTag has much richer information about types and thus can be used to distinguish between first level and nested types. Following invocation of typeTag function from TypeTag class shows that all types can be distinguished.

typeTag[Int]
res4: reflect.runtime.universe.TypeTag[Int] = TypeTag[Int]

typeTag[String]
res5: reflect.runtime.universe.TypeTag[String] = TypeTag[String]

typeTag[List[Int]]
res6: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

typeTag[List[String]]
res7: reflect.runtime.universe.TypeTag[List[String]] = TypeTag[scala.List[String]]

typeTag[List[Set[String]]]
res8: reflect.runtime.universe.TypeTag[List[Set[String]]] = TypeTag[scala.List[Set[String]]]

To get type information from TypeTag, use ‘tpe’ method. Using TypeTag, we can get information about the type passed. But we cannot find the type of an object. In following code, we can check if the passed type T is of some specific type(say List[Int]) but we cannot find type of ‘x’. That is why we used x:T in this example and not x:Any because with TypeTag, we cannot compare x with a Type like we did in ClassTag

//we do not use this signature
def checkTypeListInt[T](x:Any)(implicit tag:TypeTag[T]): Boolean= {

//instead we use T
def checkTypeListInt[T](x:T)(implicit tag:TypeTag[T]): Boolean= {
 tag.tpe match { //we cannot do 'x match' here with TypeTag
 case TypeRef(utype, usymbol, args) if(args.toString() == "List(Int)") => true
 case _ => false
 }
}

checkTypeListInt[List[Int]](List(1)) //true
checkTypeListInt[List[String]](List("1")) //false type here is List[String], not List[Int]

//if we had used x:Any, this would have been possible which is incorrect
checkTypeListInt[List[Int]](List("1")) //we mention List[Int] but pass a String

3 thoughts on “Commonly used programming constructs in Scala

Leave a comment