So ramping up with the Scala 101 series, I thought now is an appropriate juncture to introduce control structures in Scala. To a certain extent, working with the Scala language presents a vista wherein the developer is afforded
much greater freedom than in many other environments, but therein lies a great many choices and a sense of responsibility. As such, I’ve consciously tried to restrict this post to covering some of the main flavors and options for control-flow and iteration within Scala, how they differ and provide examples of usage. I expect this to be something of a ‘white knuckle ride’ through the building blocks of the language and hope to provide both a contextual guide and a point of reference for usage. My ideal is that coupled with a knowledge of the basics of the how Class and Objects differ and are constructed in Scala, this should provide a launchpad to be able to write productive Scala code.
Note: this coverage doesn’t claim, or even attempt, to be authoritative, but tries to cover some of the iteration styles and constructs I have found most useful.
So without further ado, let the click-clacking of chains purr along as we start our ascent..
if()
e.g. if() as a ternary expression
val result = if (1 > 2) true else false
Note: – if the evaluation fails to return a result than the original variable (if already initialised) will fail to execute due to type mismatches, or the variable (if non-initialised) will be initialised to AnyVal.
– initialised vals cannot be reassigned as they are immutable
e.g. if as a ternary with just one return value declared
var result2 = if(1>2) 1 // assigns result2 = AnyVal
while and do.. while
…are considered
loops and not
expressions in Scala as they don’t have to return ‘
interesting‘ types (i.e. they can return the Unit type, which is Scala’s equivalent of void in Java). Many functional languages tend to dispense with the
while() construct completely and instead rely on recursion as a control abstraction, but, given the multi-paradigm nature of Scala (and it’s use as a gateway language towards the functional paradigm), these are supported as built-in control structures. Consequently, while loops are often eshewed in favour of a more functional style of doing interation (such as using
recursion). Nonetheless, here’s a sample of standard
while and
do.. while syntax:
e.g. while() and do.. while()
var i = 0
while(i < 3) println("Hello " + {i = i + 1; i} )
or
do { println("Hello " + { i = i + 1; i } ) } while(i < 3)
for(i to/until)
..uses similar syntax to the enhanced for loop in Java 5 with some notable additions, namely: expression scoped value use type inference; the iterated value is intialised using a
generator and multiple generators can be inlined in the expression, also;
filter support is baked into the expression. A simple example should make this clearer:
e.g. Generators and filters in a for() comprehension
for(i <- 1 to 4; j <- 1 until 4 if (i * j % 2 == 0)) { println(i * j) }
As displayed above, generator bounds can be specified using either ‘to’ or ‘until’ and values can be assigned during each iteration. Simlarly filters allow multiple statements (separated by semi-colons) to prune which values enter the body of the
expression. Iterations using for() are called
expressions, (as opposed to
loops) as they
usually return a value and can be used to initialise a variable. One subtlety here is that implementation and desire to return values from the for() construct is infered, (by the compiler), by the context in which the expression is used. Digging a little deeper, we can see that there are actually two
styles of for loop, one imperative and the other functional.
The imperative style is recognisable for the fact that it ‘yields’ a result (which is returned from the expression), whereas the imperative style is used for its
side effects (such as in the example shown above). For the more inquisitive reader, a thorough extrapolation of the intricacies of for() expressions are very well covered
here. This is an excellent article which describes how for() expressions in Scala are actually
closures and why this can give surprising results. As an appetite moistener here are a few droplets of the detail:
- clauses contained in for expressions are actually closures and, as such, they fail to ‘break’ in the expected imperative sense.
- eager evaluation is the default implementation of most for() expressions.
- ‘real-time’ (i.e. lazy) evaluation of break clauses is possible using the .view of the collection. (Note: view replaces the [now] deprecated .projection method)
- however… to actually exit the closure early, the break condition should be evaluated against the projected surrogate object.
e.g. An example for() comprehension using the functional form:
var multiplesOfTwo = for(i <- 1 to 10 if i % 2 == 0) yield i
e.g. A lazy evaluated for() comprehension with a break gate
var gate = true // note this has to be a 'var' type, as we hope to reassign the variable during iteration
for(i <- testList.view.takeWhile(i => gate)) if(i < 5) println(i) else gate = false
Note: As the view and projection operations are on List, (and as the name suggests), I don’t believe it is possible to apply these ‘escape semantics‘ to a set of values created by a generator. However, given this use case, the while() loop will satisfy these needs:
e.g. using a while() to escape non-Collection iteration
var index = 1
var escapeGate = false
while(index < 10000000 && !escapeGate) {
if(index > 5) escapeGate = true
println(index)
i += 1
}
Useful operations on Lists
Lists are one of the major, (if not
the main), collection types in Scala, and can be both easily created and manipulated. Next we’ll look at some typical List operations and their use. Some of the value members description below has been gratuitously harvested from the excellent
Scaladoc.
e.g. Creating a List
val testList = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
or
val testList = 1 :: 2 :: 3 :: 4 :: 5 :: 6 :: 7 :: 8 :: 9 :: 10 :: Nil
Note: the :: operation is right associative. As a general rule, operations that end in a colon tend to be right associative. Also ::: operation can be used to concatenate multiple Lists into a new List. Joining of Lists of different types results in a new List of elements of the ‘ lowest common denominator‘ type for the individual lists
e.g. Inheritence amongst List elements
class Car; class Vovlo extends Car; class VW extends Car;
var lcdList = List(new Volvo()) ::: List(new VW()) // returns a List of Type 'Car'
Also Note: That List supports trivial operations such as head (to return the first element of the list) and tail (which returns the contents of the list after the first elements of the List). No further detail on these operations will be presented here.
foreach()
Use: Applies a function to every element of a list. This looping construct is used for the side effects that are created
Example:
testList foreach(x => println(x*2))
Note: If the function takes a single parameter, method parameters don’t have to be listed explicitly. Also, the underscore ‘_’ wildcard can replace a whole parameter list. (N.B. There is a further nuance for the underscore wildcard, where it is followed by an asterisk when used as an array substitution, e.g. ‘_*’):
e.g. foreach with inferred parameters
testList foreach(println)
and
testList foreach(println _)
filter()
Use: Selects all elements from a List that satisfy a given predicate.
Example:
val evensList = testList filter (_ % 2 == 0)
Note: Order of elements in the returned List is retained from the Source List.
partition()
Use: Splits a list into 2 lists based upon a predicate submitted, and returns a Tuple2 containing the returned Lists.
Example:
val tupleWrappedOddAndEvenLists = testList partition (_ % 2 == 0)
// the individual elements of the Tuple can be accessed in the usual way, e.g.:
println(tupleWrappedOddAndEvenLists._1)
Note: Tuple support and usage in Scala extends beyond acting as a simple wrapper to input/return multiple variables, as they can also be used in extractors, and hence reused in pattern matches. Both topics are beyond the scope of this article.
forall()
Use: Returns a boolean indicating whether all elements of a List pass a given predicate
Example:
testList forall(a => a.isInstanceOf[Int])
exists()
Use: Tests whether any element of a given List is true according to a given predicate
Example:
testList exists(a => a == 4)
map()
Use: Applies a function to all elements of a List and returns a new List containing the results.
Example:
val doubledList = testList map (_ * 2)
flatMap()
Use: Similar to the map operation, but will concatenate the results into a single List. For example, given a List of Lists and a function as input parameters, a single (transformed) List is returned.
Example:
val palindromeList = List(testList, testList reverse) flatMap (_.toList)
Note: This function is particularly useful when trying to obtain a single List of (transformed) leaf nodes from a tree structure, e.g. file system browsing or for getting a consolidated list of financial/sports markets transformed into a required domain model structure.
fold[Left/Right]()
Use: Applies a given function to all elements of a List to return an aggregated value. Practically, this is indespensible when doing terse concatenation or summation operations. The difference between foldLeft and foldRight is the direction in which the function is applied. This is of note of operations that are
noncommutative.
Example:
(1 foldLeft testList) (_-_) // This takes the seed value for the consolidated return as 1, before applying the muliplication operation on all elements of the palindromeList (effectively adding the results into a running total)
// to see the difference in foldLeft and foldRight for noncommutative operations, let's look at the minus operations applied with a foldLeft and a foldRight (using the shorthand syntax), and observe how the results differ:
(1 /: testList) (_-_) // left fold on the minus operation returns -54
(testList :\ 1) (_-_) // right fold on the minus operation returns -4
Note: The shortcut syntax for the fold operations are /: (for foldLeft) and :\ (for foldRight). The ‘/:’ naming is such, that the operation actually ‘looks like’ the direction in which the fold is occuring, with the semi-colon suggesting from which direction input is being garnered. Therefore, (1 foldLeft palindromeList) (_*_) above is equivalent to (1 /: palindromeList) (_*_)
Related: Catamorphism
reduce[Left/Right]()
Use: Recursively applies a given function to elements from of a List. The resulting value from the first iteration is used as the primary input for the subsequent iteration, and this is pattern of substitution is then applied recursively.
Example:
testList reduceLeft ((a, b) => if (a > b) a else b)
So far, this is a scattering of some of the various control structures, abstractions and List operations available in Scala and should provide a firm foundation for performing initial
Scala katas. A final noteworthy point, is to be aware that the Scala compiler will optimise
tail-recursive calls, by wrapping any tail recursive operations in for() loops, hence reusing the runtime stack frame. Next time, I’ll try to cover one of the most powerful abstractions in the Scala language, namely pattern matching and show its’ interplay with Exception handling in Scala. Hopefully this overview has given the reader an appropriate bredth and depth of information to engage in further investigation, as well as displaying some of the power of recursion and the functional style of programming that Scala facilitates. Happy hacking !
Reference: Power with control… control structures and abstractions in Scala from our JCG partner Kingsley Davies at the Scalabound blog.