Tuesday, 7 January 2014

Back on the Old for-Comprehensions Again – Filters and Guard Conditions

NOTE: There's an update to this post, based on a Google+ comment from Chris Phelps.  Cheers Chris!
I’m back on Scala’s for comprehension construct, courtesy of chapter 2 of Cay Horstmann’sScala for the Impatient”.  As you’d expect from a book with such a title, he wastes no time in getting to the point, and after a quick (thou comprehensive) intro to the basic single-generator-and-little-else flavour he’s off into multiple generators:
for (i <- 1 to 3; j <- 1 to 3)
    print((10 * i + j) + " ") // Prints 11 12 13 21 22 23 31 32 33

from Scala for the Impatient, by Cay Horstmann, pp 20
The generators here are in the first set of parens, and give us the i and j variables which are then used in the body of the loop. All very clear. 
Then we’re onto the addition of guard conditions:
for (i <- 1 to 3; j <- 1 to 3 if i != j)    print((10 * i + j) + " ") // Prints 12 13 21 23 31 32
from Scala for the Impatient, by Cay Horstmann, pp20
Now before we had “filters” which seemed to do a similar thing – only continue evaluation of that iteration of the loop if they evaluated to true themselves - but filters were applied in the body whereas here the guard condition is found within the generator parens, at least in this example.
A bit of reading ahead soon reveals that these “filters” and “guard conditions” seem to be the same thing, but things just looked different because there are two valid forms of the for-comprehension syntax – one with parens and semi-colons (Cay’s way in) and the other with curly braces (the Atomic Scala opener).  But before we make any sweeping statements, lets check to make sure.  Here’s what happens when we start with a parens-version and convert it to the curly-braces flavour:
for (i <- 1 to 3; from = 4 - i; j <- from to 3)
  print((10 * i + j) + " ")
// Prints 13 22 23 31 32 33

for {             // added a newline and replaced the open-paren with a curly brace
  i <- 1 to 3     // added a newline and dropped the semicolon
  from = 4 – i    // added a newline and dropped the semicolon
  j <- from to 3  // added a newline and dropped the semicolon
}                 // added a newline and replaced the close-paren with a curly brace
print((10 * i + j) + " ")
// Still prints 13 22 23 31 32 33

adapted from Scala for the Impatient, by Cay Horstmann, pp20
As expected, they are the same.  I’m not sure if that added flexibility makes me happy or sad.  I can however cope with the different ways of referring to filters/guard-conditions. But wait, there’s no time for emotion, yield is about to be introduced.
And here again another subtlety I wasn’t previously aware of seems to have arisen – without a yield, what we have is called a “for-loop”, but once we’re yielding it’s a “for-comprehension”.  However, as I am already aware, yielding creates a collection filled with objects of the type output by the first generator, so it’s swings and roundabouts.
Update (08/02/2014): Chris Phelps (one of my Scala-mentors, for which I'm exceedingly grateful) comments: "Underneath the syntactic sugar, the "filter" or "guard" actually uses a variant of the filter method (withFilter), so if you thought those filters were related to the filter method, well spotted."  Looks like I'm on the right track.

No comments:

Post a Comment