“For Comprehensions” (or just “Comprehensions”) are famously (on this blog at least) where I “lost it” during the TypeSafe’s Fast Track to Scala course right back at the start of this, and despite tackling it again on my own via Scala in Action it’s still not properly stuck. At the time, the “losing it” pretty much felt like I had little familiar to grip onto. Nothing looked as I’d expected it (the syntax for example) and the terminology wrapped around things that were tossed out to help me was alien too. That first post (linked above) already makes clear I’m not coming at Scala from the traditional CompSci background, and in many respects, this unholy combination of factors is precisely why I’m diving into this – I know this is an area I am both weak in, and will benefit greatly from learning. The second post (again linked above), while a lot more positive, still flounders a little in the big-ness of it all. This post is a stripping back, and a bit of solid progress forward too.
The “Comprehensions” Atom
So now I’m tackling them again; the Atomic Scala aversion therapy approach has got me up to this point, and I’ve just had my first trip back into the water at the scene where I had my terrible experience last time. But this time I’m prepared. I’ve seen the basic Scala for construct on its own, stripped down and simple. I’ve also been steadily introduced to much basic but needed terminology – “expression” for example, and “infix” - and built up a solid and dependable understanding of it. Besides this, I’ve slowly jettisoned by must-be-C-like syntax dependence with some hardcore functions-as-objects fiddling and parentheses and semi-colons have been ripped away from me left, right and centre, but I’m still comfortable.
But even with all this in the mental kit-bag, I’m going to take this slowly. I’m fortunate that this is also the approach Bruce and Dianne feel is appropriate. Typically a new concept is introduced not only with the hardcore details, but also with some lighter discussion of why, or how Scala folks mostly tend to use something. They are also very up front about when they’ve avoided looking under such-and-such a rock for now; but they do point out the rocks, so you know there’ll be a return for more at a later date.
One such example in this Atom is the discussion around the possible flavours of for-comprehension using either parentheses or curly braces (Atomic Scala, pp.177). A cursory glance at this topic, considering the (surface) similarity of the Scala for-comprehension with the Java for loop, might indicate that starting an introduction to such significant syntax with surrounding parens rather than curly braces might be more intuitive. It’s not. My biggest mistake was to bring anything of the Java for with me when I started on this Scala construct. By going in first with a multi-line formatting and curly braces at the top and bottom, announcing (subtly) a block, the authors give me something to read, line-by-line. To me, it’s a bonus that this happens to also be the way most Scala code is written.
Now that I have the metre of the construct we’re looking at, I can begin to look at the individual elements. The first step is to re-visit the generator. The statement beginning “the for loop you saw in For Loops was a comprehension with a single generator…” (my italics) implies to the careful reader that things will get harier later on in the generator space - but not yet - we’re sticking with one. I’ve already commented that I like this syntax. (And just for the record, I think the Java for syntax, and by extension the C/C++ syntax is terrible.) Lets keep moving.
Next up is the addition of some filters. These are expressed in the form of if expressions which I’ve also seen a lot previously. It’s beginning to look as if every element that is pulled out of the input by the generator (or should I say generated?) drops down through the various filters which follow, only proceeding if the expression returns true.
The last piece in this puzzle are some definitions. While these aren’t picked out as explicitly as the other two constructs, it seems clear that these are the remaining statements which all seem to be allocating values to vars or vars, either ones which are scoped with in the comprehension body or outwith it. isOdd = (n % 2 != 0) is one such definition. The authors note the lack of val / var declaration for this and I do too. Scala is managing things for us here.
Putting It All Back Together
The final step is to pull it all back together to see what we have in totality – and I’m finding that this is easiest by my building a mini, mental pseudo-Backus-Naur Form. (See how nicely formatted my mind is? :D ):
for ([generator]) {
[filter | definition] (*)
}
Please note, I find B.-N.F. as illegible as anything else, and the above chunk makes no attempt to be valid in any way. (It’s pseudo B.-N. F. remember). But it does provide me a way of representing what I’m building up on the page/screen.
When we do this it seems clear that we have an incredibly powerful tool for working our ways through collections and filtering each of the elements in turn to restrict things to only the ones we really need, using definitions as required to make our code clean and clear.
But What About yield?
Helpfully for me, this just made sense. Having come across the same keyword in Ruby (though it’s used differently, with closures) I already had a mental construct of what this keyword should do to a running system that fits use-case just as nicely. Even when we get to the multi-line-block variety Lets add it to the mini-mental pseudo-B.-N. F.:
for ([generator]) {
[filter | definition] (*)
} [expression using element from the left-hand-side of the generator | yield] {
[expression using element from the left-hand-side of the generator] (*)
}
What Next?
I’ve been around this game long enough to know that it’s not going to stay as simple as I currently have it in my head. But this feels nice and solid enough a piece of scaffolding to be getting on with. I’m not going to speculate on where the additional complexity is going to come from. I’m happy for now to roll this elegant piece of syntax around my brain pan for a while and see how it feels.