Search code examples
scalanested-loops

Scala - Nested Iteration (Odersky et al., 2010)


Following Code is from Odersky et al. (p. 167) on "Programming in Scala".

val filesHere = (new java.io.File(".")).listFiles

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.*")

The code will not compile without it because "the Scala compiler will not infer semi-colons while inside parentheses" (ibid, p. 167). Question 1: Why is the code not equivalent to:

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.*")

Question 2: Why is the semicolon after the first if condition needed in the code above? What role does it play?


Solution

  • Your for-loop has a couple of generators and filters:

    for (
      file <- filesHere                    // generator
      if file.getName.endsWith(".scala");  // filter
      line <- fileLines(file)              // generator
      if line.trim.matches(pattern)        // filter
    ) println(file + ": " + line.trim)
    

    In Scala, generators and corresponding filters can be placed within a for comprehension. This link might give a little more details on the very topic.

    As to the semi-colon, it's needed just exactly as the compiler says: the Scala compiler will not infer semi-colons while inside parentheses.

    Your for-loop is no difference from:

    for (
      file <- filesHere if file.getName.endsWith(".scala");
      line <- fileLines(file) if line.trim.matches(pattern)
    ) println(file + ": " + line.trim)
    

    Enclosing a code block with braces { ... } allows you to skip the requirement of explicit semi-colons.