Built-in control structures:
Scala has only a handful of built-in control structures. The only control structures are if, while, for, try, match, and function calls. The reason Scala has so few is that it has included function literals since its inception. Instead of accumulating one higher-level control structure after another in the base syntax, Scala accumulates them in libraries.
1. If expressions
Scala's if works just like in many other languages. It tests a condition and then executes one of two code branches depending on whether the condition holds true. Here is a common example, written in an imperative style:
var filename = "default.txt" if (!args.isEmpty) filename = args(0) |
This code declares a variable, filename, and initializes it to a default value. It then uses and if expression to check whether any arguments were supplied to the program. If so, it changes the variable to hold the value specified in the argument list. If no arguments were supplied, it leaves the variable set to the default value.
2. While loops
Scala's while loop behaves as in other languages. It has a condition and a body, and the body is executed over and over as long as the condition holds true.
example:
def gcdLoop(x: Long, y: Long): Long = { var a = x var b = y while (a != 0) { val temp = a a = b % a b = temp } b } |
Scala also has a do-while loop. This works like the while loop except that it tests the condition after the loop body instead of before.
Below shows a Scala script that uses a do-while to echo lines read from the standard input until an empty line is entered:
var line = "" do { line = readLine() println("Read: "+ line) } while (line != "") |
3. For expressions
Scala's for expression is a Swiss army knife of iteration. It lets you combine a few simple ingredients in different ways to express a wide variety of iterations. Simple uses enable common tasks such as iterating through a sequence of integers. More advanced expressions can iterate over multiple collections of different kinds, can filter out elements based on arbitrary conditions, and can produce new collections.
Iteration through collections
The simplest thing you can do is to iterate through all the elements of a collection.
For example, below shows some code that prints out all files in the current directory. The I/O is performed using the Java API. First, we create a java.io.File on the current directory, ".", and call its listFiles method. This method returns an array of File objects, one per directory and file contained in the current directory. We store the resulting array in the filesHere variable.
val filesHere = (new java.io.File(".")).listFiles for (file <- filesHere) println(file) |
Filtering
Sometimes you do not want to iterate through a collection in its entirety. You want to filter it down to some subset. You can do this with a for expression by adding a filter: an if clause inside the for's parentheses.
For example, the code shown below lists only those files in the current directory whose names end with ".scala":
val filesHere = (new java.io.File(".")).listFiles for (file <- filesHere if file.getName.endsWith(".scala")) println(file) |
Nested iteration
If you add multiple <- clauses, you will get nested "loops." For example, the for expression shown below has two nested loops. The outer loop iterates through filesHere, and the inner loop iterates through fileLines(file) for any file that ends with .scala.
def fileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines.toList def grep(pattern: String) = for ( file <- filesHere if file.getName.endsWith(".scala"); line <- fileLines(file) if line.trim.matches(pattern) ) println(file +": "+ line.trim) grep(".*gcd.*") |
Mid-stream variable bindings
Note that the previous code repeats the expression line.trim. This is a non-trivial computation, so you might want to only compute it once. You can do this by binding the result to a new variable using an equals sign (=). The bound variable is introduced and used just like a val, only with the val keyword left out.
Below shows an example.
def grep(pattern: String) = for { file <- filesHere if file.getName.endsWith(".scala") line <- fileLines(file) trimmed = line.trim if trimmed.matches(pattern) } println(file +": "+ trimmed) grep(".*gcd.*") |
Producing a new collection
While all of the examples so far have operated on the iterated values and then forgotten them, you can also generate a value to remember for each iteration. To do so, you prefix the body of the for expression by the keyword yield. For example, here is a function that identifies the .scala files and stores them in an array:
def scalaFiles = for { file <- filesHere if file.getName.ends with(".scala") } yield file |
Each time the body of the for expression executes it produces one value, in this case simply file. 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. In this case, the result is an Array[File], because filesHere is an array and the type of the yielded expression is File.
4. Exception handling with try expressions
Scala's exceptions behave just like in many other languages. Instead of returning a value in the normal way, a method can terminate by throwing an exception. The method's caller can either catch and handle that exception, or it can itself simply terminate, in which case the exception propagates to the caller's caller. The exception propagates in this way, unwinding the call stack until a method handles it or there are no more methods left.
Throwing exceptions
Throwing an exception looks the same as in Java. You create an exception object and then you throw it with the throw keyword:
throw new IllegalArgumentException |
Catching exceptions
You catch exceptions using the syntax shown below. The syntax for catch clauses was chosen for its consistency with an important part of Scala: pattern matching. Pattern matching, a powerful feature.
import java.io.FileReader import java.io.FileNotFoundException import java.io.IOException try { val f = new FileReader("input.txt") // Use and close file } catch { case ex: FileNotFoundException => // Handle missing file case ex: IOException => // Handle other I/O error } |
The finally clause
You can wrap an expression with a finally clause if you want to cause some code to execute no matter how the expression terminates. For example, you might want to be sure an open file gets closed even if a method exits by throwing an exception. Below shown an example.
import java.io.FileReader val file = new FileReader("input.txt") try { // Use the file } finally { file.close() // Be sure to close the file } |
Yielding a value
As with most other Scala control structures, try-catch-finally results in a value. For example, below shows how you can try to parse a URL but use a default value if the URL is badly formed. The result is that of the try clause if no exception is thrown, or the relevant catch clause if an exception is thrown and caught. If an exception is thrown but not caught, the expression has no result at all. The value computed in the finally clause, if there is one, is dropped. Usually, finally clauses do some kind of clean up such as closing a file; they should not normally change the value computed in the main body or a catch clause of the try.
import java.net.URL import java.net.MalformedURLException def urlFor(path: String) = try { new URL(path) } catch { case e: MalformedURLException => new URL("http://www.scala-lang.org") } |
5. Match expressions
Scala's match expression lets you select from several alternatives, just like switch statements in other languages. In general, a match expression lets you select using arbitrary patterns. For now, just consider using match to select among several alternatives.
As an example, the script below reads a food name from the argument list and prints a companion to that food. This match expression examines firstArg, which has been set to the first argument out of the argument list. If it is the string "salt", it prints "pepper", while if it is the string "chips", it prints "salsa", and so on. The default case is specified with an underscore (_), a wildcard symbol frequently used in Scala as a placeholder for a completely unknown value.
val firstArg = if (args.length > 0) args(0) else "" firstArg match { case "salt" => println("pepper") case "chips" => println("salsa") case "eggs" => println("bacon") case _ => println("huh?") } |
0 Comments