In Less is More, we discussed how Scala has a few, well-chosen, and independent features that combine in countless ways to provide the expressive features of Scala as we know it. However, the discussion remained mostly conceptual, with few examples. In this post, I want to highlight several more examples that will make the whole argument more concrete, so that you don’t need to take my word for it.

First, let us look again at at the append method on an IntList.

class IntList {
  def append(x: Int): IntList = ???
}

This is still used as follows:

val l1 = new IntList  // l1 = []
val l2 = l1.append(1) // l2 = [1]
val l3 = l2.append(2) // l3 = [1, 2]
val l4 = l3.append(3) // l4 = [1, 2, 3]

Simple and no surprise, however a tiny bit verbose. In a language like Go, the append method can take an arbitrary number of elements in one call. In Scala, that would look like this:

val l1 = new IntList 
val l4 = l1.append(1, 2, 3)

Of course, the compiler will not accept such code with the above definition of append. But there is a way to fix it, using the varargs syntax:

class IntList {
  def append(xs: Int*): IntList = ???
}

A type T* is essentially equivalent to Seq[T] and is easy to use at the definition site. The caller is free to call the function with any number of argurments of type T, or in this case of integers: l.append(1), l.append(1,2), or l.append(1,2,3). This is a syntax that makes sense when it is just as common to use the function with a single argument as it is to use it with two or three. Of course, if the function is always called with a long list of elements, it’s better to just define a function that takes a sequence as an argument. By itself, this is a fairly simple features, the compiler simply wraps the variable number of arguments into a basic Sequence datastructure.

Alright, time to introduce a new Scala feature: magic methods. There are a few method names that enable a special syntax for method invocation. For example, there is the setter syntax, name_= which enables a lightweight getter and setter syntax for classes. Another common example is apply, which sorts of transforms the instances of the class into functions. Indeed, an instance of a class with an apply method can be applied to a value, just like a function. Sequences are examples of classes with apply method:

trait Seq[T] {
  def apply(i: Int): T = ???
}
val s = Seq(1, 2, 3)
s(1) // returns 2

Here we used s as a function by applying it on 1, which essentially returned the second element of the sequence. The compiler implicitly rewrites all such function application as s.apply(1). These methods might seem magic, and they are, but there is just a small well-defined set of them, and they can be used to achieve very neat effects.

One such effect is when they are combined with companion objects. Companion objects are yet another convenient Scala feature. They can essentially be defined next to a class to provide some static methods. Back at our sequence example:

trait Seq[T]
object Seq {
  def apply[T](elements: T*): Seq[T] = ???
}
val s = Seq(1, 2, 3)

We defined a Seq object, which is a short-hand notation for a singleton object of an unnamed type that contains just the methods defined within that object. In the example above, it’s just the apply method. You can then use Seq.apply(???) in any context. Notice the statement below, we have seen it before. We have used it as an intuitive way to create a sequence with three elements. At the time it might have looked like built-in syntax, but now you can see that it’s just a combination of a singleton object Seq with an apply magic method that takes a variable number of arguments — three independently useful and well-defined features.

Let’s conclude this tour of Scala by looking at for-comprehensions. I will not go into the details of how for-comprehensions are desugared into map, flatMap, and filter, as this would require to introduce all these methods. But you can find a comprehensive (no pun intended) discussion of this on the official Scala website. The key take-away is that any type can be made compatible with a for-notation, as long as it defines the proper methods.

I would like to look at one particularly popular for-notation:

for(i <- 1 to 10)
  println(i)

At first glance, it looks like a totally new and magic notation. We know two standard ways of defining variables in scala, val and var, this notation looks like it’s introducing a third one. And it does, but it’s a bit more subtle than that. It’s a special notation to bind each element of the list on the right-hand side, so more generally it will be written as for(x <- xs) ..., with xs being a sequence. And in many ways, this is equivalent to xs.foreach(x => ...). So here for is really some syntactic sugar to define a lambda function on top of a sequence. The reason for introducing this has to do with the powerful for-comprehension features that were mentioned above.

What about the 1 to 10? Well, remember infix notation? If you do, you would know that we are really looking at 1.to(10). Does that look confusing? Well that might be because you got used to most other languages treating integers as some kind of built-in type, but that’s not how it works in Scala. In scala, an Int is just a regular type with its class definition (the details are slightly more messy, but let’s gloss over them for the sake of the argument):

class Int {
  def to(u: Int): Seq[Int] = ???
}
val xs = 1 to 5 // returns Seq(1,2,3,4,5)

Well now we are fully equipped to rewrite the original for loop:

for(i <- 1 to 10)
  println(i)

as:

(1.to(10)).foreach(i => println(i))

which definitely feels less magical, but also definitely more ugly. No wonder Scala programmers will favor the former notation.