Scala Loops
Scala has only a handful of built-in control structures. The only control structures if you check, are if, while, for, try, match, and function calls and almost all of Scala's control structures result in some value. What's benefit of expression? Let's say we have :
If expressions:
var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)
Here If condition is changing filename and as we know mutation is dangerous, specially in parallel runs. Also it's difficult to test.
In Scala way we can write it as expression
val filename = if (!args.isEmpty) args(0) else "default.txt"
It uses a val instead of a var. Using a val is the functional style, and it helps you in much the same way as a final variable in Java.
Just like other languages Scala also has while loop, It has a condition and a body, and the body is executed over and over as long as the condition holds true :
var i = 10
while (i > 1) {
println(i)
i -= 1
}
Scala also has a do-while loop. The while and do-while constructs are called "loops," not expressions, because they don't result in an interesting value.
Because the while loop results in no value, it is often left out of pure functional languages. Such languages have expressions, not loops. Scala includes the while loop nonetheless because sometimes an imperative solution can be more readable. For example, if you want to code an algorithm that repeats a process until some condition changes, a while loop can express it directly while the functional alternative, which likely uses recursion, may be less obvious to some readers of the code.
Then what is the Scala way of looping ?
Scala has For expressions :
I would call it Swiss army knife because along with common tasks such as iterating through a sequence of integers. More advanced expressions can iterate over multiple collections of different kinds, filter out elements based on arbitrary conditions, and produce new collections. We will see how:
To start with, let's say I want to list all the files from current directory :
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
println(file)
"file <- filesHere" syntax, which is called a generator, we iterate through the elements of filesHere. If you put multiple generator then you can get nested iterations.
for example:
for (file <- filesHere; number <- List(23,45,2,8,7))
println(file+" with "+number)
As you saw above for expression syntax works for any kind of collection, not just arrays.
One convenient special case is the Range type,
for (i <- 1 to 4)
println("Iteration " + i)
If you don't want to include the upper bound of the range in the values that are iterated over, use until instead of to: scala> for (i <- 1 until 4)
// Below Java way is not common in Scala...
for (i <- 0 to filesHere.length - 1)
println(filesHere(i))
The reason this kind of iteration is less common in Scala is that you can just iterate over the collection directly. When you do, your code becomes shorter and you sidestep many of the off-by-one errors that can arise when iterating through arrays. Should you start at 0 or 1? Should you add -1, +1, or nothing to the final index? Such questions are easily answered, but also easily answered wrong. It is safer to avoid such questions entirely.
Filtering :
Sometimes you don't 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, in previous example you want all the scala files only:
First solution that will come in our mind is:
for (file <- filesHere)
if (file.getName.endsWith(".scala"))
println(file)
But if filtering is that much essential then why not add it with For expression itself:
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)
let's enhance our requirement, now we want to read all scala files and if a line match with our condition then print that :
for {
file <- filesHere
if file.getName.endsWith(".scala")
line <- fileLines(file)
if line.trim.matches(pattern)
} println(s"$file: $line.trim")
You might have noticed that I have used 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 Scala compiler will not infer semicolons while inside parentheses. It raises a question when to use curly braces and when to use parentheses. Mostly they are interchangeable except in case of Case classes, where you can't use parentheses. More details at Click for more details
In above example line.trim called twice, which can be expensive and redundant operation to solve this Scala for expression provide :
Mid-stream variable bindings :
for {
file <- filesHere
if file.getName.endsWith(".scala")
line <- fileLines(file)
trimmed = line.trim
if trimmed.matches(pattern)
} println(s"$file: $trimmed")
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:
val result:Array[String] = for {
file <- filesHere
if file.getName.endsWith(".scala")
line <- fileLines(file)
trimmed = line.trim
if trimmed.matches(pattern)
} yield trimmed
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.
You may ask when we have map, Flatmap functions then why we need to know For expression ?
It's because for expression can make complicated problems simple and easy to understand:
Let's take an example, we have a list of persons, each defined as an instance of a class Person. Class Person has fields indicating the person's name, whether he or she is male, and his or her children.
case class Person(name: String,
isMale: Boolean,
children: Person*)
val lara = Person("Lara", false)
val bob = Person("Bob", true)
val julie = Person("Julie", false, lara, bob)
val persons = List(lara, bob, julie)
we want to find out the names of all pairs of mothers and their children in that list.
Using map, filters we can create the query but it would be very complicated.
for (p <- persons; if !p.isMale; c <- p.children)
yield (p.name, c.name)
But questions is still there, which one is good higher order function map, flat map or for expression, so to answer this at the end scala compiler converts for expressions into map, flatMap, and withFilter.
If we understand how this conversion happens then this is a good news for people like me, who first think in imperative style of programming. We can write logic using for expression then convert into higher order functions map, flatmap :
for (x <- expr1) yield expr2
will translate to expr1.map(x => expr2)
Translating for expressions starting with a generator and a filter
for (x <- expr1 if expr2) yield expr3
will translate to-> for (x <- expr1 withFilter (x => expr2)) yield expr3
then to -> expr1 withFilter (x => expr2) map (x => expr3)
if there are further elements following the filter. If seq is an arbitrary sequence of generators, definitions, and filters, then:
for (x <- expr1 if expr2; seq) yield expr3
will translate to for (x <- expr1 withFilter expr2; seq) yield expr3
Translating for expressions starting with two generators :
for (x <- expr1; y <- expr2; seq) yield expr3
The for expression above is translated to an application of flatMap:
expr1.flatMap(x => for (y <- expr2; seq) yield expr3)