tags:
- "#scala"
scala-notes
book-body
Contents
local
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:
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
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
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'.
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.
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.
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();
}
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)
}
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) { // <-- !
axy.baz()
axy.foo("hello")
axy.bar(42)
}
}
class C extends Base2 with TraitA with TraitB {
override def print() { println("C"); super.print() }
}
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 }
}
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