1   Notes On Scala

1.1   References

1.2   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) }

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

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

# This is how scala supports duck typing ...
def quacker(duck: {def quack(value: String): String}) {
    println (duck.quack("Quack"))
}

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.

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
    

1.2.1   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) {
   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’.

1.2.2   Notes From Martin Odersky’s Talk

See: https://www.youtube.com/watch?v=ecekSCX3B4Q

  • 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.

1.2.3   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.

1.3   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