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 "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.
|
![]() |
![]() |
As a first example, look at Int.
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. |
foldLeftFor 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).
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.
z, which is of type B (whatever that is).op, which is of type (B, A) => B ; that is, a function whose first argument is of type B, whose second argument is of type A, and whose return type is B.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> 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 = |
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.
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, , 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 |
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 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,
|
|
|
In function
|
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.