val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(0) = "Hello"
compiler transforms to:
greetStrings.update(0, "Hello")
Arrays are mutable, lists are immutable.
Operator Associativity
Operators are left associative unless they end with a colon. Therefore, in 1 :: twoThree, the :: method is invoked on twoThree, passing in 1, like this: twoThree.::(1).
List Append
Class List does offer an “append” operation —it’s written :+ and is explained in Chapter 24— but this operation is rarely used, because the time it takes to append to a list grows linearly with the size of the list, whereas prepending with :: takes constant time. Your options if you want to build a list efficiently by appending elements is to prepend them, then when you’re done call reverse; or use a ListBuffer, a mutable list that does offer an append operation, and when you’re done call toList.
Tuples
Access tuple fields with ._1, ._2, etc.
val pair = (99, "Luftballons")
Scala infers the type of the tuple to be Tuple2[Int, String] the Scala library only defines them up to Tuple22. You can’t access the elements of a tuple like the elements of a list, for example, with “pair(0)” because a list’s apply method always returns the same type, but each element of a tuple may be a different type: _1 can have one result type, _2 another, and so on. These _N numbers are one-based, instead of zero-based, because starting with 1 is a tradition set by other languages with statically typed tuples, such as Haskell and ML.
reduceLeft()
val longestLine = lines.reduceLeft((a, b) => if (a.length > b.length) a else b)
The reduceLeft method applies the passed function to the first two elements in lines, then applies it to the result of the first application and the next element in lines, and so on, all the way through the list. On each such application, the result will be the longest line encountered so far, because the passed function, (a, b) => if (a.length > b.length) a else b, returns the longest of the two passed strings
Unit
The following are all the same:
def add(b: Byte): Unit = {
sum += b
}
sum += b
}
def add(b: Byte): Unit = sum += b
def add(b: Byte) { sum += b }
def f(): Unit = "this String gets lost"
def g() { "this String gets lost too" }
def h() = { "this String gets returned!" } // h: ()StringClass vs Object
A class is a blueprint for objects. Once you define a class, you can create objects from the class blueprint with the keyword new.
Classes in Scala cannot have static members - instead, Scala
has singleton objects. A singleton object definition looks like a class definition,
except instead of the keyword class you use the keyword object.
When a singleton object shares the same name with a class, it is called that class’s companion object. You must define both the class and its companion object in the same source file. The class is called the companion class of the singleton object. A class and its companion object can access each other’s private members.
If you are a Java programmer, one way to think of singleton objects is as the home for any static methods you might have written in Java. You can invoke methods on singleton objects using a similar syntax. A singleton object is more than a holder of static methods, however. It is a first-class object. You can think of a singleton object’s name, therefore, as a “name tag” attached to the object.
The type is defined by the class not the object. However, singleton objects extend a superclass and can mix in traits. Given each singleton object is an instance of its superclasses and mixed-in traits, you can invoke its methods via these types, refer to it from variables of these types, and pass it to methods expecting these types.
One difference between classes and singleton objects is that singleton objects cannot take parameters, whereas classes can. Because you can’t instantiate a singleton object with the new keyword, you have no way to pass parameters to it. Each singleton object is implemented as an instance of a synthetic class referenced from a static variable, so they have the same initialization semantics as Java statics. (The name of the synthetic class is the object name plus a dollar sign. Thus the synthetic class for the singleton object named ChecksumAccumulator is ChecksumAccumulator$.) In particular, a singleton object is initialized the first time some code accesses it.
Standalone Object
A singleton object that does not share the same name with a companion class is called a standalone object. You can use standalone objects for many purposes, including collecting related utility methods together, or defining an entry point to a Scala application
Scala Application
Any standalone object with a main method of the proper signature (takes one parameter - an Array[String] - and has a result type of Unit) can be used as the entry point into an application.
object Blah {
def main(args:Array[String]) { println "hello" }
}
Implicit Imports
Scala implicitly imports members of packages:
- java.lang
- scala
- scala.Predef (println, assert, etc)
You can name .scala files anything you want, no matter what Scala classes or code you put in them - recommend naming non-script .scala files after the classes they contain.
Can import methods from any object (not just singleton objects) with "import Object.method" (no need for "static" keyword).
A script must end in a result expression (not a definition).
fsc - Fast Scala Compiler
Starts up a daemon to eliminate JVM load time on every compile.
use fsc -shutdown to kill.
scala
The actual mechanism that the "scala" program uses to “interpret” a Scala source file is that it compiles the Scala source code to Java bytecodes, loads them immediately via a class loader, and executes them.
Application Trait
object Blah extends Application {
println "hello"
}
Application trait declares a main method of the appropriate signature, which your singleton object inherits, making it usable as a Scala application. The code between the curly braces is collected into a primary constructor of the singleton object, and is executed when the class is initialized.
Application Trait Shortcomings
- you can’t use this trait if you need to access command-line arguments, because the args array isn’t available
- because of some restrictions in the JVM threading model, you need an explicit main method if your program is multi-threaded
- some implementations of the JVM do not optimize the initialization code of an object which is executed by the Application trait
scala.Byte, scala.Short, scala.Int, scala.Long, and scala.Char are called integral types. The integral types plus Float scala.and scala.Double are called numeric types.
Operators
Can use any method in infix operator notation.
E.g. "abc" indexOf 'b' // Scala invokes "abc".indexOf('b')
Can even use it for methods that take more than one argument.
E.g. s indexOf ('o', 5) // Scala invokes s.indexOf(’o’, 5)
Prefix Operators
In prefix notation, the operand is to the right of the operator. Some examples of prefix operators are 2.0, !found, and ~0xFF. These prefix operators are a shorthand way of invoking methods. In this case, however, the name of the method has “unary_” prepended to the operator character. For instance,
Scala will transform the expression 2.0 into the method invocation “(2.0).unary_”.
The only identifiers that can be used as prefix operators are +, -,!, and ~. Thus, if you define a method named unary_!, you could invoke that method on a value or variable of the appropriate type using prefix operator notation, such as !p. But if you define a method named unary_*, you wouldn’t be able to use prefix operator notation, because * isn’t one of the four identifiers that can be used as prefix operators. You could invoke the method normally, as in p.unary_*, but if you attempted to invoke it via *p, Scala will parse it as if you’d written *.p, which is probably not what you had in mind!
Postfix Operators
Postfix operators are methods that take no arguments, when they are invoked without a dot or parentheses.
The convention is that you include parentheses if the method has side effects, such as println(), but you can leave them off if the method has no side effects, such as toLowerCase invoked on a String.
These are all the same:
s.toLowerCase()
s.toLowerCase
s toLowerCase
Conditional Initialisation
val filename =
if (!args.isEmpty) args(0)
else "default.txt"
Using a val instead of a var better supports equational reasoning. The introduced variable is equal to the expression that computes it, assuming that expression has no side effects
Using vals helps you safely make this kind of refactoring as your code evolves over time.
Look for opportunities to use vals. They can make your code both easier to read and easier to refactor.
It turns out that a value (and in fact, only one value) exists whose type is Unit. It is called the unit value and is written (). The existence of () is how Scala’s Unit differs from Java’s void.
Comparing values of type Unit and String using != will always yield true. Whereas in Java, assignment results in the value assigned, in this case a line from
the standard input, in Scala assignment always results in the unit value, (). Thus, the value of the assignment "line = readLine()" will always be () and never be "".
Challenge while loops in your code in the same way you challenge vars.
For Loops
The expression to the right of the <- symbol in a for expression can be any type that has certain methods, in this case foreach, with appropriate signatures.
Can iterate through Arrays, Ranges
Filters
filter: an if clause inside the for’s parentheses
for (
file
if file.getName.endsWith(".scala")
) println(file)
If you add multiple <- clauses, you will get nested "loops."
If you prefer, you can use curly braces instead of parentheses to surround the generators and filters. One advantage to using curly braces is that you can leave off some of the semicolons that are needed when you use parentheses, because as explained in Section 4.2, the Scala compiler will not infer semicolons while inside parentheses.
Mid-stream variable bindings
for {
file
line
if trimmed.matches(pattern)
} println(file +": "+ trimmed)
Producing a new collection
To generate a value to remember for each iteration you prefix the body of the for expression by the keyword yield. Each time the body of the for expression executes it produces one value. When the for expression completes, the result will include all of the yielded values contained in a single collection. The type of the resulting collection is based on the kind of collections processed in the iteration clauses.
For example, here is a function that identifies the .scala files and stores them in an array:
def scalaFiles =
for {
file
} yield file
Exceptions
"throw" is an expression that has a result type.
it is safe to treat a thrown exception as any kind of value whatsoever. Any context that tries to use the return from a throw will never get to do so, and thus no harm will come.
an exception throw has type Nothing
You can use a throw as an expression even though it will never actually evaluate to anything
E.g. One branch of an if computes a value, while the other throws an exception and computes Nothing. The type of the whole if expression is then the type of that branch which does compute something.
Catch pattern matching
...
} catch {
case ex: FileNotFoundException => // Handle missing file
case ex: IOException => // Handle other I/O error
}
Scala does not require you to catch checked exceptions, or declare them in a throws clause. You can declare a throws clause if you wish with the @throws annotation, but it is not required
try-catch-finally results in a value