scala-notes

book-body

Notes On Scala

Contents

local

References

Examples

scala>  val i = 10               # Constant integer.
scala>  var v = 20               # Variable v, that can be reassigned. Type inferred as Int.

scala>  var v:Int = 20.0         # Error because v is Int and 20.0 is Float.
scala>  var v:Int = 20.0.toInt   # Now OK.

scala>  def square(a:Int) : Int = {  a * a }
scala>  def square(a:Int) =  { a * a }          # This is allowed since type inference possible.

scala>  var f = square
square: (a: Int)Int

# To be more precise, the function type of square is:  Int => Int 
scala>  var  f: Int => Int = square

# Void type is declared as Unit. The Any type is like Java Object.
scala>  def print(a:Any): Unit = { println(a) }

# Procedures can drop the = in declaration, but not recommended :
scala>  def print(a:Any)  { println(a) }

# Function literals ..
val  add_one : Int => Int = (x) => x + 1

# x => x + 1 is a function literal (aka lamda or anonymous function). 

# if statement ...

val s =  if (x > 0) "positive" else -1 
s: Any = -1                                     # could be either String or Int. So it has type 'Any'

#
# Fixed length Arrays ... Mutable but can not grow in size ...
# 
var b = new Array[Int](10)           # Generics use [] not <>. size is 10.
var b = Array(10, 20, 30)            # Size is 3.

#
# Variable size Array ... is ArrayBuffer
#
val b = ArrayBuffer[Int]()  // Or new ArrayBuffer[Int] 
b +=    1                # Array append
b ++=   Array(10, 20)    # Array merge

#
# Transforming arrays ..  multiple of even numbers only ...
#
b.filter(_ % 2 == 0).map(2 * _)

## Inspect type ...

import scala.reflect.ClassTag
val a:Any  = 5
a.getClass
ClassTag(a.getClass)
:type a                        # Works only in REPL shell.

a.getClass.getSimpleName == "Integer"
res24: Boolean = true

## While loop ...

while (n > 0) {  
   r = r * n  
   n -= 1 
} 

## For loop ...

for (i <- 1 to n)   r = r * i 
val s = "Hello" 
var sum = 0 
for (i <- 0 until s.length) // Last value for i is s.length - 1  
   sum += s(i) 

// In this example, there is actually no need to use indexes. 
var sum = 0 
for (ch <- "Hello") sum += ch

for (i <- 1 to 3; j <- 1 to 3 if i != j) print((10 * i + j) + " ")  // Prints 12 13 21 23 31 32

for (i <- 1 to 3; from = 4 - i; j <- from to 3) print((10 * i + j) + " ")  // Prints 13 22 23 31 32 33

scala> for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
res30: scala.collection.immutable.IndexedSeq[Char] = Vector(H, e, l, l, o, I, f, m, m, p)

scala> for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
res31: String = HIeflmlmop

# This is how scala supports duck typing ...

def quacker(duck: {def quack(value: String): String}) = {
    println (duck.quack("Quack"))
}

## switch equivalent match case statement ...

val month = i match {
  case 1  => "January"
  case 2  => "February"
  case 3  => "March"
  case _  => "unknown"
}

// msg is bound with the variable serv
serv match {
  case msg if msg.startsWith("chat")  => println("yes string starts with chat")   // msg is bound to serv
  case msg if msg.startsWith("hello") => println("yes string starts with hello")  // msg is again bound to serv
  case hi  if hi.startsWith("hi")     => println("yes string starts with hi")     // hi is bound to variable serv
  case _ => null
}

def parseArgument(arg: String) = arg match {
    case "-h" | "--help" => displayHelp
    case "-v" | "--version" => displayVerion
    case whatever => unknownArgument(whatever)
 }


// You can see that in the definition of the function we are not expecting a particular class or type. 
// We are specifying Structural Type -- any type that has a method with the signature quack(string: String): String.

Collections :

// See http://www.scala-lang.org/docu/files/collections-api/collections.html
//     http://docs.scala-lang.org/overviews/collections/performance-characteristics.html
//
import scala.collection.mutable
import scala.collection.immutable._

// These are mostly traits or abstract classes ... some of them defined in both mutable and immutable...
//    Traversable, Iterable, Seq, IndexedSeq, Iterator, Stream, Vector, StringBuilder, and Range.
//
// immutable.Seq() is backed by List(); mutable.Seq() is backed by ArrayBuffer.
// i.e.  val a = Seq(10, 20)  will create a List or ArrayBuffer for you.
//
// Vector is concrete class and immutable and supports fast random updates !!!
// Note: Supporting update on immutable type means, new object is returned !!! 
//
// Vector is default backing store for immutable indexed sequences. 
//

Mutable collection examples :

import scala.collection.mutable._

scala> var nums = ArrayBuffer(1, 2, 3)
nums: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3)

// add one element. Use nums.append(4) or ...
scala> nums += 4
res0: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4)

// add two or more elements (method has a varargs parameter)   Note: Tuple is mainly used for Args.
scala> nums += (5, 6)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6)

// add elements from another collection
scala> nums ++= List(7, 8)
res2: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)

// Similar semantics apply for -= and --= operators.

// Mutable list is another option ...

import scala.collection.mutable.MutableList
val x = MutableList(1, 2, 3, 4, 5)
x += 6 // MutableList(1, 2, 3, 4, 5, 6)
x ++= MutableList(7, 8, 9) // MutableList(1, 2, 3, 4, 5, 6, 7, 8, 9)

// ListBuffer is another option ... with almost same functionality. 
// LinkedList is another option but now a days never used by applications. (Internally used)

Immutable Collection Examples :

// Lists is immutable. Bit inefficient if you keep adding elements.

var l = 1.0 :: 5.5 :: Nil
>> l: List[Double] = List(1.0, 5.5)

// Append single element to list.

scala> l :+ 10.0
res55: List[Double] = List(1.0, 5.5, 10.0)    # This is a new copy though

scala> l
res56: List[Double] = List(1.0, 5.5)          # Original list unmodified.

// Append list to list.

scala> l ::: List(2.2, 3.7)
res1: List[Double] = List(1.0, 5.5, 2.2, 3.7)

// Note: Nil is predefined empty list.
scala> Nil
res57: scala.collection.immutable.Nil.type = List()

Type Casting :

scala> 42.asInstanceOf[Double]
res52: Double = 42.0

val objects = Array("a", 1)
val arrayOfObject = objects.asInstanceOf[Array[Object]]
AJavaClass.sendObjects(arrayOfObject)

Note: Generic type arguments occur within square brackets rather than angle brackets.

You can use code block for intermediate computation and to hide temp variables :

val distance = { val dx = x - x0; val dy = y - y0; sqrt(dx * dx + dy * dy) }

Use for loop boundaries wisely :

for (i <- 1 to 10)         println(i)     // print 1 .. 10
for (i <- 0 until 10)      println(i)     // print 0 .. 9
for (e <- my_collection)   println(e)     // print each element in collection.

Default and Named Arguments :

// You can provide default arguments 
def decorate(str: String, left: String = "[", right: String = "]") =  left + str + right 

scala>  deocorate("Hello")                                         # Prints   "[Hello]"
scala>  deocorate("Hello", "<", ">")                               # Prints   "<Hello>"
scala>  deocorate(left = "<", str = "Hello", "right = ">")         # Prints   "<Hello>"

Variable Args and Argument unpacking :

def sum(args: Int*) = {  var result = 0 ; for (arg <- args) result += arg ; result } 
sum(10, 20, 30)         // Yields 60
val s = sum(1 to 5)     // Error since arg must be Int
val s = sum(1 to 5: _*) // Argument unpacking: Consider 1 to 5 as an argument sequence 
                        // my_collection:_* unpacks the elements and pass them as separate args.

Lazy value declaration. If value never referred, then following file never loaded :

lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString

Java has no checked exceptions. Typical Exception :

throw new IllegalArgumentException("x should not be negative") 

Exception expression is of type "Nothing". (If x 10 else throw my_exception) has type Int only. The else type Nothing is ignored.

The syntax for catching exceptions is modeled after the pattern-matching syntax:

try {   process(new URL("http://horstmann.com/fred-tiny.gif")) } 
catch {  
        case  _: MalformedURLException => println("Bad URL: " + url)  
        case ex: IOException          => ex.printStackTrace() 
} 

Function prototype definitions:

Note that the following function copies an ArrayBuffer[A] into an Array[B]. Here, B is allowed to be a supertype of A. For example, you can copy from an ArrayBuffer[Int] to an Array[Any]. At first reading, just ignore the [B >: A] and replace B with A :

def copyToArray[B >: A] (xs: Array[B]): Unit

Let us define max(a, b) as long as they are comparable :

def max[B >: A] (implicit cmp: Ordering[B]): A

A must have a supertype B for which an implicit object of type Ordering[B] exists. Such an ordering exists for numbers, strings, and other types with the Ordered trait. Also for classes that implement the Java Comparable interface.

Interoperating with Java:

For example, the java.lang.ProcessBuilder class has a constructor with a List<String> parameter. Here is how you can call it from Scala:

import scala.collection.JavaConversions.bufferAsJavaList 
import scala.collection.mutable.ArrayBuffer 
val command = ArrayBuffer("ls", "-al", "/home/cay") 
val pb = new ProcessBuilder(command) // Scala to Java 

Maps and Tuples:

  • Scala has a pleasant syntax for creating, querying, and traversing maps.
  • You need to choose between mutable and immutable maps.
  • By default, you get a hash map, but you can also get a tree map.
  • You can easily convert between Scala and Java maps.
  • Tuples are useful for aggregating values. Tuples members can be anytype. Non homogenous.

Examples:

val scores = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8) 

// The -> operator makes a pair. The value of "Alice" -> 10 is ("Alice", 10). Easier to read.

scores += ("Bob" -> 10, "Fred" -> 7) 
scores -= "Alice" 

for ((k, v) <- map) process k and v    // Map Iteration
scores.keySet // A set such as Set("Bob", "Cindy", "Fred", "Alice") 
for (v <- scores.values) println(v)    // Prints 10 8 7 10 or some permutation thereof 

// To reverse a map—that is, switch keys and values ... 
for ((k, v) <- map) yield (v, k)
  • The (1, 3.14, "Fred") is a tuple of type Tuple3[Int, Double, java.lang.String]

  • Above is also written as (Int, Double, java.lang.String)

  • You can access tuple components using 1, 2, etc suffix :

    val t = (1, 3.14, "Fred")
    val second = t._2         // Sets second to 3.14; Note positions start with 1 not 0.
    
    // NOTE: You can write t._2 as t _2 (with a space instead of a period), but not t_2.
    
  • Usually, it is better to use pattern matching to get at the components of a tuple :

    val (first, second, third) = t // Sets first to 1, second to 3.14, third to "Fred" 
    // You can use a _ if you don’t need all components: 
    val (first, second, _) = t
    

Zipping

Bundle together values so that they can be processed together :

val symbols = Array("<", "-", ">") 
val counts = Array(2, 10, 2) 
val pairs = symbols.zip(counts) 
// yields an array of pairs Array(("<", 2), ("-", 10), (">", 2)) 
// The pairs can then be processed together: 
for ((s, n) <- pairs) print(s * n) 

You can convert key, value pairs into map :

keys.zip(values).toMap

Passing code block as function argument

Arbitrary code block can be passed as function arg. Code block can be imagined as 0 arg anonymous function, however scala uses 'empty type declaration' for code block :

def doThisTwice (code: => Boolean) {       // This is procedure. Note the missing "=" !!!
   code
   code
}

doThisTwice  {
    println("Arbitrary code here ... ")  // Code will not be executed here inline. Only passed from here.
    true
}

This is also called as 'Call by Name'.

Avoiding null - Use of Option, None, Some

The null is completely typeless and problematic. Avoid using it. In scala you have support for Option object.

In short :

Type Hierarchy:

      Option
       /   \
      /     \
     /       \
   Some     None

Option is container base which can be empty or full. While Some(x) represent full with 'x' being present in the container, None represents empty.

We use None where we used null to avoid exception.

Notes From Martin Odersky's Talk

  • Goal: Compose, Match, Group, Recurse
  • Even statements are like expression, so they are composable. e.g. if statement.
  • Match statement can match anything
  • Grouping and nesting is possible. e.g. function inside function not possible in Java.
  • CRUD - is for imperative programming. Functional programming focuses on transforming and aggregating.

Instantiation, apply method, companion object

The apply method of a class makes the corresponding object to act like function. i.e. overload () operator :

my_obj = new MyClass(10, 20)  # Class constructor called.
val some  = my_obj(30)        # Class apply() method called on object.

val x = MyClass(10, 20)       # Error. You can not instantiate class name without new. Only object allowed.

Arrays implement this apply method so that my_array(10) yields 10th element. i.e. () used like [] for indexing.

Simple example :

class MyAdder(x: Int) {
  def apply(y: Int) = x + y
}

val adder = new MyAdder(2)
val result = adder(4) // equivalent to x.apply(4)

It's often use in companion object, to provide a nice factory method for a class or a trait, here is an example:

trait A {
  val x: Int
  def myComplexStrategy: Int
}

object A {
  def apply(x: Int): A = new MyA(x)

  private class MyA(val x: Int) extends A {
    val myComplexStrategy = 42
  }
}

From the scala standard library, you might look at how scala.collection.Seq is implemented: Seq is a trait, thus new Seq(1, 2) won't compile but thanks to companion object and apply, you can call Seq(1, 2) and the implementation is chosen by the companion object.

Using Classes

class Person {  var age = 0 } 
$ scalac Person.scala 
$ scala -private Person 
Compiled from "Person.scala" 
public class Person extends java.lang.Object implements scala.ScalaObject {
     private int age;
     public  int age();
     public  void age_$eq(int);    // Setter is called  age_= 
     public  Person(); 
}

Primary Vs Auxilary Constructors

class Person(val name: String, val age: Int) {

   // name, age predefined as class members !!!

   println("Just constructed another person")   // Executed part of default constructor.

   def description = name + " is " + age + " years old" 

   def this(name: String) { // An auxiliary constructor    
       // this(name, 0) // Calls primary constructor if so desired.
       this.name = name   
    }

    // Tip: Eliminate aux constructors by using default args values.
    // e.g. class Person(val name: String = "", val age: Int = 0)
}

Singleton Object

Object is used as singleton Class. It can not have constructors. To emulate situation where you have Java static and instance methods, you use companion object in scala.

Object can extend a class and/or one or more traits. Traits in Scala are best described as "interfaces that can provide concrete members."

So multiple inheritance sort of supported i.e. you can not extend 2 classes, but can extend multiple traits with each trait having some implementations :

trait X { def foo(s: String) }

trait Y { def bar(i: Int) }

class A {
    def baz() { println("Hello, world!") }
}

class B {
    def bazFooBar(axy: A with X with Y) { // &lt;-- !
        axy.baz()
        axy.foo("hello")
        axy.bar(42)
    }
}

class C extends Base2 with TraitA with TraitB {
  override def print() { println("C"); super.print() }
}

Companion Object

The class and its companion object can access each other’s private features. They must be located in the same source file.

Companion object contains all methods that you would use as static methods in Java :

class Account {  
    val id = Account.newUniqueNumber()  
    private var balance = 0.0  
    def deposit(amount: Double) { balance += amount }  
}

object Account { // The companion object  
    private var lastNumber = 0  
    private def newUniqueNumber() = { lastNumber += 1; lastNumber } 
} 

Conversions between Java and Scala

import collection.JavaConverters._
import collection.JavaConversions._        
  • You can implicity convert between Java and Scala types, more convenient, but may be bit dangerous.

  • Prfer JavaConverters. Eg:

    java.util.Iterator                     | asScala             | scala.collection.Iterator
    java.util.Enumeration                  | asScala             | scala.collection.Iterator
    java.lang.Iterable                     | asScala             | scala.collection.Iterable
    java.util.Collection                   | asScala             | scala.collection.Iterable
    java.util.List                         | asScala             | scala.collection.mutable.Buffer
    java.util.Set                          | asScala             | scala.collection.mutable.Set
    

Basic Syntax

  • Beware of missing = in a function definition.
  • An if expression has a value.
  • A block has a value—the value of its last expression.
  • The Scala for loop is like an enhanced Java for loop.
  • Semicolons are (mostly) optional.
  • The void type is Unit.
  • Avoid using return in a function.

Tips

  • If a class or object has apply() method, then that object can be used like a function.
  • Mixed-type expression, such as if (x > 0) "positive" else -1 is considered as common super type 'Any'.
  • tuple unpacking etc automatically done.
  • match-case statement can be used against strings or class objects or lists, etc. If the class contains unapply() method, that is used for matching case statement.
  • Every java object has companion object in Scala: StringOps for String, RichInt for Integer or int, RichDouble, etc. See scaladoc for the methods in them.
  • Scala has no checked exceptions
  • Exceptions work just like in Java or C++, but there is no checked exceptions and you use a pattern matching syntax for catch.
  • a + b is short-hand for a.+(b); It is not operator overloading but due to +, -, * being allowed in method names !!!