2008-09-28

Scala for Pythonistas: Part 2

This is the second installment in the series that examines features that a beginner to intermediate level Pythonista would use, and their Scala equivalents. I am not an expert-level Pythonista, so I won't cover any advanced features.

For-loops

We have already seen the for-comprehensions (see part 1) in Scala. The for-loop construct is similar, but without the yield bit.

# Python
acc = 0
for x in xs:
  acc += x

// Scala
var acc = 0
for (x <- xs) {
  acc += x
} 

No break or continue

Scala does not support break and continue. Additional if-else must be used to simulate continue. To simulate break, an additional flag has to be maintained.

Scala does this to keep the syntax of the core language small and to avoid the complications of adding another control mechanism. Some would point out that this is related to the whole "goto considered harmful" business, because break and continue are restricted forms of goto. However, I think, Scala is trying to push functional-style programming, and that this isn't a misguided attempt to enforce "goto-less programming".

Before I describe how to simulate break in Scala, I want to point out that using break and continue judiciously can make code clearer and more concise than its convoluted work around. So, if your language provides a break construct, do not avoid it just for fanatical adherence to "anti-goto-ism".

Donald Knuth pointed out that fanatically avoiding all goto-like constructs might lead to unnecessarily complex and obscure code[Knuth].

Simulating break

Consider how you will simulate break in Scala. You will notice that the use of break is more concise and clearer. The work-around has to maintain an additional condition variable (think of C-style for-loop):

# Python
acc = 0
for x in xs:
  acc += x
  if test(x, acc):
     break

// Scala
var acc = 0
var continue = true
for (x <- xs.takeWhile(e => continue)) {
  acc += x
  continue = !test(x, acc)
} 

There are cases, when we can avoid the additional condition variable. This is when our test depends only on the values accessible from the takeWhile's predicate, and when the values accessible from the takeWhile's predicate are the appropriate values, and when the initial case would not be false. And we can also rephrase the test case to avoid the negation.

Depending on which would be clearer, you are most likely to use other constructs or break the computation into smaller functions (Scala's compile time and Java VM's runtime optimizations should, hopefully, keep the function call cost a bit lower than that of Python).

Simulating continue

Simulation of continue is described in the discussion of while loops below (which applies to for-loops as well).

While Loops

While loop construct is similar to that of Python (and most other languages). Consider the following Python code:

res = 0
while test(res):
  res += work(res)

Here is the Scala equivalent:

var res = 0
while (test(res)) {
  res += work(res)
}
Simulating break
# Python
res = 0
while test1(res):
  res += work(res)
  if test2(res):
     break

// Scala
var res = 0
var continue = test1(res)
while (continue) {
  res += work(res)
  continue = test1(res) && !test2(res)
}

Scala does not provide break or continue, so we need to use a “continue” flag and update the flag. Alternatively, we can use functional constructs or express it in another way; which may or may not be clearer.

Of course, we can do this kind of programming in Python as well. In fact, some, especially those in academia, might insist on programming the above Python as follows:

res = 0
do_continue = test1(res)
while do_continue:
  res += work(res)
  do_continue = test1(res) and not test2(res)

In this particular case, I think I will stick to using break in Python.

Simulating continue

Similarly, Python's continue can be simulated by an additional if-else block.

# Python (I find this particular case more confusing)
res = 0
while test1(res):
  res += work1(res)
  if test2(res):
     continue
  work2(res)

// Scala
var res = 0
while (test1(res)) {
  res += work1(res)
  if (!test2(res)) {
     work2(res)
  }
}

# Python; better
res = 0
while test1(res):
  res += work1(res)
  if not test2(res):
     work2(res)

In this case, in Python, Java, and Scala, I prefer to use a nested if-else block over continue. The only advantage continue has over the nested-if-else is that you can avoid another level of nesting, which isn't worth it in this case. However, the continue construct might be more appropriate when using a fail/exit-early programming style.

Simulating enumerate

Although the for-each constructs in Python and Scala makes iterating over items in an iterable convenient, it becomes a bit annoying when we also need to keep track of the index. In Python, the solution is to use a wrapper generator, like the built-in enumerate. In Scala, the zipWithIndex method of the iterable is used instead:

# Python
for i, x in enumerate(xs):
  print(i, x)

// Scala
for ((x, i) <- xs.zipWithIndex) {
  println(i + " " + x)
}

Wrap-up

Hopefully, I can continue cover more of this fun stuff in later installments.

References

Notes

[Knuth: Structured Goto]
In "Structured Programming with go-to Statements" describes cases where goto-like constructs are clearer and more concise than convoluted code to avoid goto-like constructs.