-
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”