What is the accepted/recommended syntax for Scala code with lots of method-chaining?

overthink picture overthink · Jun 24, 2011 · Viewed 9.1k times · Source

In Scala I tend to favour writing large chained expressions over many smaller expressions with val assignments. At my company we've sort of evolved a style for this type of code. Here's a totally contrived example (idea is to show an expression with lots of chained calls):

import scala.util.Random
val table = (1 to 10) map { (Random.nextInt(100), _) } toMap

def foo: List[Int] =
  (1 to 100)
    .view
    .map { _ + 3 }
    .filter { _ > 10 }
    .flatMap { table.get }
    .take(3)
    .toList

Daniel Spiewak's Scala Style Guide (pdf), which I generally like, suggests the leading dot notation in the chained method calls may be bad (see doc: Method Invocation / Higher-Order Functions), though it doesn't cover multi-line expressions like this directly.

Is there another, more accepted/idiomatic way to write the function foo above?

UPDATE: 28-Jun-2011

Lots of great answers and discussion below. There doesn't appear to be a 100% "you must do it this way" answer, so I'm going to accept the most popular answer by votes, which is currently the for comprehension approach. Personally, I think I'm going to stick with the leading-dot notation for now and accept the risks that come with it.

Answer

Kevin Wright picture Kevin Wright · Jun 24, 2011

The example is slightly unrealistic, but for complex expressions, it's often far cleaner to use a comprehension:

def foo = {
  val results = for {
    x <- (1 to 100).view
    y = x + 3 if y > 10
    z <- table get y
  } yield z
  (results take 3).toList
}

The other advantage here is that you can name intermediate stages of the computation, and make it more self-documenting.

If brevity is your goal though, this can easily be made into a one-liner (the point-free style helps here):

def foo = (1 to 100).view.map{3+}.filter{10<}.flatMap{table.get}.take(3).toList
//or
def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList

and, as always, optimise your algorithm where possible:

def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList
def foo = ((4 to 103).view filter {10<} flatMap {table.get} take 3).toList
def foo = ((11 to 103).view flatMap {table.get} take 3).toList