Understanding the Scala API
© David Matuszek, 2011

Scala has the reputation of being a difficult language. Whether true or not, the Scala API is full of obscure and intimidating notation, and navigation is not immediately obvious. The purpose of this document is to help the reader understand and use the API.

The Scala API is somewhat similar to the Java API. It uses a frame on the left to display package names; clicking on a package name brings up a display of that package in the frame on the right. The O icon and C icon icons indicate whether the item is an object or a class (or both); T icon indicates a trait.

"Display packages only" does what it says; "display all entities" displays packages and the classes they contain.

Each frame has a search box; searching is only done within the same frame. Entering a word in the left-hand search box searches for packages and classes whose name contains that word. Entering a word in the right-hand search box searches the displayed package, class, object, or trait.

To search for a particular method, click on the initial letter of the method in the displayed alphabet, then find it in the frame on the right.

 


Int

As a first example, look at Int.

  1. The C with the curled-up corner shows that you are looking at the documentation for the class; click on it to reveal the documentation for the companion object. There may be useful methods in both the class and the object.

  2. Int is in the scala package.

  3. This is the documentation for Int. It is a subclass of Any and AnyVal.
    • Any is the superclass of everything.
    • AnyVal is a trait that defines the types which, in Java, would be primitives.Traits are like Java interfaces, but can contain fully-defined, inheritable methods. It is common in Scala for a class to “mix in” (inherit from) multiple traits.
    • A sister class, AnyRef, corresponds to Java's Object type.

  4. You can view the source for any class or object by clicking on this link. The source code is often easier to understand than you might think; so if you don't understand a method, you might try looking at the source.

  5. You can control the order in which the methods are displayed, and which ones to display.

  6. This is a description of the != method. In Scala, “operators” are implemented as methods, so n != x is equivalent to (n).!=(x).
    • The receiver of the message is this, which is an Int. The receiver is not shown in the method description.
    • The argument to the method is x, which is of type Double.
    • The method returns a Boolean result.
    The method is overloaded; below it are other versions of != which take different parameters.

Finally, there is no actual description of what the method does. In many cases this is hardly necessary; but current Scala documentation is extremely incomplete. In many cases, looking at the source (see #4) is your best option.


Multiple parameter lists: foldLeft

For a slightly more complex example, look for the foldLeft method (by clicking on F in the alphabet, then using control-F to search the page).

foldLeft

You will find links to ten different places. Which to choose? It turns out that almost all of them provide the exact same information; the exception is TraversableOnceMethods, which is a trait. Going to, for example, TraversableOnce, and clicking on Known Subclasses (near Linear Supertypes, near #4 above), you can discover that this method can be applied to a huge number of things--Lists, Sets, Maps, and Arrays (actually, ArrayLike) are among them.

Going to one of these links--then using control-F yet again, to find it on the page--gives a description that begins with

    def foldLeft [B] (z: B)(op: (B, A) ⇒ B): B

The [B] indicates that this method is parameterized with a type, which is used in the rest of the description. You can basically ignore this, and just look at the uses of B in the rest of the description.

foldLeft is a bit unusual in that it takes two parameter lists.

Finally, the method returns a result of type B.

Scala allows the use of an underscore to indicate a missing receiver or parameter list, resulting in a partially applied function. Because foldLeft is defined with more than one parameter list, it may be used to create partially applied functions.

For example, we might first create a List to use as a receiver,

scala> val myList = (1 to 10) toList 
 myList: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

then use foldLeft in one of the following ways:

scala> val sum = myList.foldLeft(1000)((x, y) => x + y)
sum: Int = 1055
scala> val fun2 = myList.foldLeft(_: Int)((x, y) => x + y)
fun2: (Int) => Int = 

scala> fun2(1000) // fun2 includes myList and (x, y) => x + y
res10: Int = 1055
scala> val fun1 = (_: List[Int]).foldLeft(1000)((x, y) => x + y)
fun1: (List[Int]) => Int = <function1>

scala> fun1(myList)  // fun1 includes 1000 and (x, y) => x + y
res9: Int = 1055
scala> val fun3 = myList.foldLeft(1000) _
fun3: ((Int, Int) => Int) => Int = 

scala> fun3((x, y) => x + y) // fun3 includes myList and 1000
res11: Int = 1055

The above examples show the omission of one parameter (or receiver); multiple omissions are also possible. In cases where Scala cannot infer the type of the omitted value, an explicit type annotation is required.

Promotion to supertypes

All the elements of a list must be of the “same type.” The “cons” operator, ::, is defined in List as

def :: (x: A): List[A]

which says that adding a value of type A to a List returns a List of A. This is simple enough. But there is an additional definition,

def :: [B >: A] (x: B): List[B]

which says that if B is a superclass of A, B >: A, then adding a B to a List of A will result in a List of B. For example, given the following definitions:

class Animal
class Cat extends Animal
class Dog extends Animal
val spot = new Dog
val puff = new Cat

we observe that

scala> val pets = List(spot)
pets: List[Dog] = List(Dog@39193540)

scala> puff :: pets
res21: List[Animal] = List(Cat@2e484dfd, Dog@39193540)

Here we added puff, which is a Cat but also an Animal, to pets, which is a List[Dog], giving a new list whose type is the nearest supertype of both Cat and Dog, that is, List[Animal]. This is called covariance, and it is allowed in Scala because lists are immutable; the pets list is still a list of dogs. Arrays are mutable, therefore putting in a value of the “wrong” type is not allowed.

scala> val petArray = Array(spot)
petArray: Array[Dog] = Array(Dog@39193540)

scala> petArray(0) = puff
:14: error: type mismatch;
 found   : Cat
 required: Dog
       petArray(0) = puff
                     ^ 

By contrast, arrays in Java are covariant. The following is syntactically legal, but results in a runtime exception:

Animal[] pets = new Dog[1]; // This is Java
pets[0] = new Cat();        // Causes java.lang.ArrayStoreException

Covariance and contravariance (and invariance)

Covariance and contravariance are properties of collections. A collection of values of type A is covariant if it may be treated as a collection of values of some supertype of A. In the above section, Lists are covariant because a List[Dog] may be treated as if it were a List[Animal]. Covariance is indicated in the class definition with a + symbol, as for example,

class List [+A] extends LinearSeq[A]
                with Product
                with GenericTraversableTemplate[A, List]
                with LinearSeqOptimized[A, List[A]]

If A is a subtype of B, it does not necessarily follow that a collection of A is a subtype of a collection of B. In fact, this is only safe for immutable collections.

Contravariance is the opposite: it allows a collection of values of type A to be treated as a collection of values of some subtype of A. Contravariance is indicated in the class definition with a - symbol. The canonical example of contravariance is the Function1 trait:

trait Function1 [-T1, +R] extends AnyRef

This trait defines a function that may take as its one argument a given type T1, or any subtype of T1, and return a value of type R, or any supertype of type R. That is, it is contravariant in its argument type, and covariant in its return type. For example,

scala> class Animal
defined class Animal

scala> class Dog extends Animal
defined class Dog

scala> class Beagle extends Dog
defined class Beagle

scala> def foo(x: List[Dog]) = x
foo: (x: List[Dog])List[Dog]

scala> val an: List[Animal] = foo(List(new Beagle))
an: List[Animal] = List(Beagle@284a6c0)

Beagle is a subclass of Dog; Dog is a subclass of Animal.

 

In function foo,

  • Parameter x is contravariant; it is defined to be of type List[Dog], but a list of any subtype of Dog (such as Beagle) can be assigned to it.

  • The return type is covariant; although a List[Dog] is being returned, it can be assigned to a list of any supertype collection (such as List[Animal]).

Finally, a collection is invariant if it is neither covariant or contravariant. That is, it may hold values only of the exact type specified.

One of the best discussions of covariance and contravariance is this one, using horses and their owners.