程式扎記: [Scala IA] The Basics : Ch2. Getting Started - Controlling flow with loops, ifs and For-comprehensio

標籤

2016年7月2日 星期六

[Scala IA] The Basics : Ch2. Getting Started - Controlling flow with loops, ifs and For-comprehensio

Controlling flow with loops and ifs 
It’s a little hard to get into useful programming or scripting with Scala without the loops and ifs. Well, your wait is over. In Scala, if and else blocks work as they do in any other programming language. If the expression inside the ifevaluates to true, then the if block gets executed; otherwise, the else block is executed. The interesting part about Scala is that every statement is an expression, and its value is determined by the last expression within the statement. Assigning a value depending on some condition in Scala could look like this: 
  1. val someValue = if(some condition) value1 else value2  
For example: 
scala> val useDefault = false
useDefault: Boolean = false

scala> val configFile = if(useDefault) "default.txt" else "custom.txt"
configFile: String = custom.txt

Scala doesn’t support the ? operator as Java does, but I don’t think you’ll miss it in Scala. You can nest if/else blocks, and you can combine multiple if/else blocks using else if. Loops in Scala have all the usual suspects like the while loop and do-while, but the most interesting looping construct is for or for-comprehensions. The while and do-while loops are pretty standard, and in Scala they aren’t any different from Java or C#. The next section looks at Scala for-comprehensions. 

For-comprehensions 
A for-comprehension in Scala is like a Swiss Army knife: you can do many things with it using basic simple elements. The for expression in Scala consists of a for keyword followed by one or more enumerators surrounded by parentheses and followed by an expression block or yield expression (see figure 2.2). 
 

Before I go into yield expression, let’s look into the more traditional form of the for loop. The common pattern used in a for loop is to iterate through a collection. To print all the files in a directory that end with the .scala extension, for example, you have to do something like the following: 
  1. val files = new java.io.File(".").listFiles  
  2. for(file <- files="" nbsp="" span="">
  3.     val filename = file.getName  
  4.     if(fileName.endsWith(".scala")) println(file)  
  5. }  

The only thing that looks different from for loops in Java or C# is the expression file <- files="" font="">. In Scala this is called a generator, and the job of a generator is to iterate through a collection. The right side of the <- b=""> represents the collection—in this case, files. For each element in the collection (file in this case) it executes the for block. This is similar to the way you define a for loop in Java: 

  1. for(File file: files) {  
  2.     String filename = file.getName();  
  3.     if(filename.endsWith(".scala")) System.out.println(file);  
  4. }  
In the case of Scala, you don’t have to specify the type of file object because Scala type inference will take care of it. Apart from the generator, you can use other ingredients in a Scala for loop to simplify the previous example. 
  1. for(  
  2.     file <- files="" nbsp="" span="">
  3.     fileName = file.getName;  
  4.     if(fileName.endsWith(".scala"))  
  5. ) println(file)  
Scala for loops allow you to specify definitions and guard clauses inside the loop. In this case you’re defining a filename variable and checking whether the filename ends with .scala. The loop will execute when the given guard clause is true, so you’ll get the same result as the previous example. Note that all the variables created inside a for expression are of the val type, so they can’t be modified and hence reduce the possibility of side effects. 

As mentioned earlier, it’s possible to specify more than one generator in a Scala for loop control. The following example executes the loop for each generator and adds them: 
scala> val aList = List(1, 2, 3)
aList: List[Int] = List(1, 2, 3)

scala> val bList = List(4, 5, 6, 7)
bList: List[Int] = List(4, 5, 6, 7)

scala> for {a <- a="" alist="" b="" blist="" font="" println="">
5
6
7
8
6
7
8
9
7
8
9
10

The generators in this case are aList and bList, and when you have multiple generators, each generator is repeated for the other generator. When a = 1 for each value of b, that is, b = 4, b = 5, b = 6, the loop will be executed, and so on. I used curly braces to surround the for expression, but you don’t have to; you could use (). I tend to use curly braces when I have more than one generator and guard clause. 

The for-comprehension in Scala comes in two flavors. You’ve already seen one form in the previous examples: the imperative form. In this form you specify the statements that will get executed by the loop, and it doesn’t return anything. The other form of for-comprehension is called the functional form (sometimes it’s also called sequence comprehension). In the functional form, you tend to work with values rather than execute statements, and it does return a value. Look at the same example in functional form: 
scala> for {a <- a="" alist="" b="" blist="" font="" yield="">
res4: List[Int] = List(5, 6, 7, 8, 6, 7, 8, 9, 7, 8, 9, 10)

Instead of printing the value of a + b, you’re returning the value of the addition from the loop using the yield keyword. You’re using the same aList and bList instances in the loop control, and it returns the result as a List. Now if you want to print the result, as in the previous example, you have to loop through the result List
scala> val result = for {a <- a="" alist="" b="" blist="" font="" yield="">
result: List[Int] = List(5, 6, 7, 8, 6, 7, 8, 9, 7, 8, 9, 10) 
scala> for(r <- font="" println="" r="" result="">
5
6
7
8
6
7
8
9
7
8
9
10
It does look like the functional form is more verbose than the imperative form, but think about it. You’ve separated the computation (the adding of two numbersfrom how you’re using it—in this case, printing the result. This improves the reusability and compatibility of functions or computations, which is one of the benefits of functional programming. In the following example you reuse the result produced by the for yield loop to create an XML node: 
scala> val xmlNode = {result.mkString(",")}
xmlNode: scala.xml.Elem = 5,6,7,8,6,7,8,9,7,8,9,10

The mkString is a method defined in scala.collection.immutable.List. It takes each element in the List and concatenates each element with whatever separator you provide—in this case, a comma. Even though it doesn’t make sense, what if you try to print inside the yield expression? What will happen? Remember, everything in Scala is an expression and has a return value. If you try the following, you’ll still get a result, but the result won’t be useful because it will be a collection of units. A unit is the equivalent of void in Java, and it’s the result value of a println function used inside the yield expression: 
scala> for { a<- a="" alist="" b="" blist="" font="" println="" yield="">
...
res6: List[Unit] = List((), (), (), (), (), (), (), (), (), (), (), ())

You’ve only scratched the surface of for-comprehension, and I come back to this in chapter 4 to examine functional data structures, so hold on to your inquisitiveness until chapter 4 (or jump to that chapter). The next section moves into another functional concept: pattern matching.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!