Chapter 3. OOP in Scala

This chapter covers

  • Building a MongoDB driver using Scala classes and traits
  • Pattern matching with case classes
  • Looking into named and default arguments

Up to this point the book has been focusing on Scala’s fundamentals. This chapter introduces the object-oriented features of Scala. Object-oriented programming isn’t new, but Scala has added a few new features that aren’t available in other statically typed languages.

In this chapter you’ll build a Scala driver for MongoDB (www.mongodb.org/display/DOCS/Home). MongoDB is a scalable, document-oriented database. You’ll build this driver incrementally using the object-oriented constructs provided by Scala, and along the way I’ll explain each concept in detail. Scala has made some object-oriented innovations, and one of them is the trait. Traits are similar to abstract classes with partial implementation. You’ll explore how to use traits when building Scala applications. As you move through the chapter, you’ll learn about Scala case classes. Case classes are useful when it comes to building immutable classes, particularly in the context of concurrency and data transfer objects. Case classes also allow Scala to bridge the gap between functional programming and OOP in terms of pattern matching. Without wasting any more time, let’s learn OOP programming in Scala by building a MongoDB driver.

3.1. Building a Scala MongoDB driver: user stories

To explore Scala’s object-oriented constructs and use, let’s build a MongoDB driver. While building this example driver, you’ll dive deep into concepts for a thorough understanding. You won’t need to start from scratch because you’ll use an existing Java driver for MongoDB. You’ll build a Scala wrapper over the Java MongoDB driver. That way, you don’t have to deal with the low-level MongoDB API and can focus on your objective of learning Scala.

The user stories you’ll be implementing in your Scala wrapper driver are as follows:

As a developer, I want an easier way to connect to my MongoDB server and access document databases.

As a developer, I want to query and manage documents.

What’s a User Story?

A good way to think about a user story is as a reminder to have a conversation with your customer (in Agile, project stakeholders are called customers), which is another way to say it’s a reminder to do some just-in-time analysis. In short, user stories are slim and high-level requirements artifacts.

MongoDB is a scalable, high-performance, open source, schema-free, document-oriented database written in C++.[1] MongoDB is a document-based database that uses JSON (JavaScript Object Notation). The schema-free feature lets MongoDB store any kind of data of any structure. You don’t have to define your database tables and attributes up front. You can add or remove attributes whenever you need them. This flexibility is achieved through the document-based model. Unlike relational databases, in a document-based model records are stored as documents in which any number of fields of any length can be stored. For example, you could have the following JSON documents in a single collection (a collection in MongoDB is like a table in a traditional RDBMS):

1 “What is the Right Data Model?,” July 16, 2009, http://mng.bz/1iT0.

{ name : "Joe", x : 3.3, y : [1,2,3] }
{ name : "Kate", x : "abc" }
{ q : 456 }

In a schema-free environment, the concept of schema moves more toward the application than to the database. Like any other tool, there are pros and cons for using a schema-free database, and it depends on the solution you’re trying to solve.

The format of the document in which the information is stored in MongoDB is BSON (binary JSON). Other document-based databases like Lotus Notes (IBM) and SimpleDB (Amazon.com) use XML for information storage. JSON has an added advantage when working with web-based applications because JSON content can be easily consumed with little transformation. A great place to get a feel for MongoDB is http://try.mongodb.org. Go ahead and download MongoDB (www.mongodb.org/display/DOCS/Downloads). Then, unpack it and run the following command to start the MongoDB server:

$ bin/mongod

To connect to the MongoDB server, use the client shell that ships with the distribution of MongoDB:

$ bin/mongo
MongoDB shell version: 1.2.4
url: test
connecting to: test
type "help" for help
>

At this point you should be ready to start building the Scala wrapper driver. If you’re interested in learning more about MongoDB, look at the MongoDB tutorial (www.mongodb.org/display/DOCS/Tutorial).

3.2. Classes and constructors

To connect to the already running MongoDB server, create a Mongo client class with a hostname and port number:

<scala> class MongoClient(val host:String, val port:Int)

The class declaration looks different from the way you declare in Java or C#—you’re not only declaring the class, but also its primary constructor.

The primary constructor is a constructor that needs to be called directly or indirectly from overloaded constructors when creating the instance MongoClient. You’ll look into overloaded constructors shortly. In Scala, the primary constructor for a class is coded inline with the class definition. In this case, the constructor takes two parameters: host and port. The host parameter specifies the address of the server, and port specifies the port in which the MongoDB server is waiting for the connection.

Because all the constructor parameters are preceded by val, Scala will create immutable instance values for each of them. The following example creates an instance of a Mongo client and accesses its properties:

scala> val client = new MongoClient("127.0.0.1", 123)
client: MongoClient = MongoClient@561279c8

scala> client.port
res0: Int = 123

scala> client.host
res1: String = 127.0.0.1

Like Java or C#, Scala also uses the new keyword for creating instances of a class. But wait a minute—where’s the body of the MongoClient class? In Scala that’s optional.

You can create classes without any class body. Creating a class like a JavaBean with only a getter and setter would be easy in Scala, as in the following:

scala> class AddressBean(
    var address1:String,
    var address2:String,
    var city:String,
    var zipCode:Int)
defined class AddressBean

scala> var localAddress = new AddressBean("230 43rd street", "", "Columbus",
     43233)
localAddress: (java.lang.String, java.lang.String, java.lang.String, Int) =
     (230 43rd street,,Columbus,43233)

When parameters are prefixed with var, Scala creates mutable instance variables. The val and var prefixes are optional, and when both of them are missing, they’re treated as private instance values, not accessible to anyone outside the class:

scala> class MongoClient(host:String, port:Int)
defined class MongoClient

scala> val client = new MongoClient("localhost", 123)
client: MongoClient = MongoClient@4089f3e5

scala> client.host
<console>:7: error: value host is not a member of MongoClient
       client.host

Note that when Scala creates instance values or variables, it also creates accessors for them. At no point in time are you accessing the field directly. The following MongoClient definition is equivalent to the class MongoClient(val host:String, val port:Int) definition.

class MongoClient(private val _host:String, private val _port:Int) {
 def host = _host
 def port = _port
}

The reason I’m using private (you’ll learn about access levels later in this chapter) is so the Scala compiler doesn’t generate accessors by default. What val and var do is define a field and a getter for that field, and in the case of var an additional setter method is also created.

Most of the time you’ll have MongoDB running on the localhost with default port 27017. Wouldn’t it be nice to have an additional zero-argument constructor that defaults the host and port number so you don’t have to specify them every time? How about this:

class MongoClient(val host:String, val port:Int) {
    def this() = this("127.0.0.1", 27017)
}

To overload a constructor, name it this followed by the parameters. Constructor definition is similar to method definition except that you use the name this. Also, you can’t specify a return type as you can with other methods. The first statement in the overloaded constructors has to invoke either other overloaded constructors or the primary constructor. The following definition will throw a compilation error:

How do you add a setter method to a class?

To add a setter, you have to suffix your setter method with _=. In the following Person class, age is private so I’ll add both a getter and a setter:

class Person(var firstName:String, var lastName:String,
        private var _age:Int) {
    def age = _age
    def age_=(newAge: Int) = _age = newAge
}

Now you can use the Person class and change its age value:

val p = new Person("Nima", "Raychaudhuri", 2)
p.age = 3

The assignment p.age = 3 could be replaced by p.age_=(3). When Scala encounters an assignment like x = e, it checks whether there’s any method defined like x_= and if so, it invokes the method. The assignment interpretation is interesting in Scala, and it can mean different things based on context. For example, assignment to a function application like f(args) = e is interpreted as f.update(args). You’ll read more about function assignments later.

class MongoClient(val host:String, val port:Int) {
  def this() = {
    val defaultHost = "127.0.0.1"
    val defaultPort = 27017
    this(defaultHost, defaultPort)
  }
}

When you compile this with scalac, you get the following compilation errors:

MongoClient.scala:3: error: 'this' expected but 'val' found.
    val defaultHost = "127.0.0.1"
    ^
MongoClient.scala:4: error: '(' expected but ';' found.
    val defaultPort = 27017
^
two errors found

This poses an interesting challenge when you have to do some operation before you can invoke the other constructor. Later in this chapter, you’ll look into a companion object and see how it addresses this limitation.

To make a connection to the MongoDB you’ll use the com.mongodb.Mongo class provided by the Mongo Java driver:

class MongoClient(val host:String, val port:Int) {
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)
}
Note

I have used the Mongo Java driver version 2.10.1 for all the code in this chapter. To run the Scala Mongo wrapper code you’re going to develop in this chapter, you need to have the Java driver .jar file available in the classpath. For more information on the Java driver, visit www.mongodb.org/display/DOCS/Java+Language+Center. To compile the previous code, you have to import com.mongdb.Mongo above the class definition. You’ll learn about importing in the next section.

The underlying instance value will hold the connection to MongoDB. When Scala generates the constructor, it instantiates the underlying instance value too. Because of Scala’s scripting nature, you can write code inside the class like a script, which will get executed when the instance of the class is created (kind of like Ruby). The following example creates a class called MyScript that validates and prints the constructor input:

class MyScript(host:String) {
  require(host != null, "Have to provide host name")
  if(host == "127.0.0.1") println("host = localhost")
  else println("host = " + host)
}

And now load MyScript into Scala REPL:

scala> :load MyScript.scala
Loading MyScript.scala...
defined class MyScript

scala> val s = new MyScript("127.0.0.1")
host = localhost
s: MyScript = MyScript@401e9c3f

scala> val s = new MyScript(null)
java.lang.IllegalArgumentException: requirement failed:
    Have to provide host name
    at scala.Predef$.require(Predef.scala:117)
    at MyScript.<init>(<console>:5)

How is Scala doing this? Scala puts any inline code defined inside the class into the primary constructor of the class. If you want to validate constructor parameters, you could do that inside the class (usually at the top of the class). Let’s validate the host in the MongoClient:

class MongoClient(val host:String, val port:Int) {
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)
}

Right now the MongoClient is using an underlying instance to hold the connection to MongoDB. Another approach would be to inherit from the com.mongodb.Mongo class, and in this case you don’t have to have any instance value to hold the connection to MongoDB. To extend or inherit from a superclass, you have to use the extends keyword. The following code demonstrates how it would look if you extended from the Mongo class provided by the Java driver:

class MongoClientV2(val host:String, val port:Int)
    extends Mongo(host, port){
  require(host != null, "You have to provide a host name")
  def this() = this("127.0.0.1", 27017)
}

As shown in the previous example, you can also inline the definition of the primary constructor of a superclass. One drawback of this approach is that you can no longer validate the parameters of the primary constructor before handing it over to the superclass.

Note

When you don’t explicitly extend any class, by default that class extends the scala.AnyRef class. scala.AnyRef is the base class for all reference types (see section 3.1).

Even though extending Mongo as a superclass is a completely valid way to implement this driver, you’ll continue to use the earlier implementation because that will give you more control over what you want to expose from the Scala driver wrapper, which will be a trimmed-down version of the complete Mongo Java API. Before going any further, I’ll talk about Scala imports and packages. This will help you to work with the Mongo library and structure your code.

3.3. Packaging

A package is a special object that defines a set of member classes and objects. The Scala package lets you segregate code into logical groupings or namespaces so that they don’t conflict with each other. In Java you’re only allowed to have package at the top of the .java file, and the declaration defines the scope across the file. Scala takes a different approach for packaging. It combines Java’s declaration approach with C#’s scoped approach. You can still use the traditional Java approach and define package at the top of the Scala file, or use a scoping approach, as demonstrated in the following listing.

Listing 3.1. Declaring packages using the scoping approach
package com {
  package scalainaction {
    package mongo {
     import com.mongodb.Mongo
     class MongoClient(val host:String, val port:Int) {
        require(host != null, "You have to provide a host name")
        private val underlying = new Mongo(host, port)
        def this() = this("127.0.0.1", 27017)

     }
    }
  }
}

Here you’re creating the com.scalainaction.mongo package for the MongoClient class. The previous code is exactly equivalent to the following code, where you’re declaring the package in traditional Java style:

package com.scalainaction.mongo
import com.mongodb.Mongo
class MongoClient(val host:String, val port:Int) {
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)
}

You can also use curly braces with top-level package declarations like the following:

package com.scalainaction.mongo {
    import com.mongodb.Mongo
    class MongoClient(val host:String, val port:Int) {
      require(host != null, "You have to provide a host name")
      private val underlying = new Mongo(host, port)
      def this() = this("127.0.0.1", 27017)
    }
}

It’s a matter of style; you can use either one of them. The scoping approach shown in listing 3.1 provides more flexibility and a concise way to lay out your code in different packages. But it might quickly become confusing if you start to define multiple packages in the same file. The most widely used way in Scala code bases is the traditional way of declaring a package at the top of the Scala file. The only large, open source project I know of that uses the package-scoping approach is the Lift web framework (http://liftweb.net).

One more interesting point to note here is that Scala package declaration doesn’t have to match the folder structure of your filesystem. You’re free to declare multiple packages in the same file:

package com.persistence {
  package mongo {
     class MongoClient
  }
  package riak {
    class RiakClient
  }
  package hadoop {
    class HadoopClient
  }
}

If you save this code in a file called Packages.scala and compile it using the Scala compiler (scalac Packages.scala), you’ll notice that the Scala compiler generates class files in appropriate folders to match your package declaration. This ensures that your classes are compatible with the JVM, where package declaration has to match the folder structure in the filesystem.

Building Scala code

Scalac[2] is the compiler that comes bundled with the Scala distribution. If you’ve installed Scala as specified in chapter 2, you should have it available in your path. The Scala compiler provides lots of standard options, like deprecation, verbose, and classpath, and additional advanced options. For example, to compile the MongoClient you have to do the following:

2 “Scalac user commands,” www.scala-lang.org/docu/files/tools/scalac.html.

scalac -classpath mongo/mongo-2.10.1.jar MongoClient.scala

Invoking the Scala compiler directly for smaller examples is okay, but for larger projects I tend to use build tools like Ant, Maven, or SBT. Ant and Maven are standard tools for building Java projects. You can easily use them to build Scala projects too, but the standard build tool for Scala projects is SBT.[3] Chapter 5 discusses how to use build tools to build Scala projects. For now, let’s stick to scalac.

3 Mark Harrah, “SBT, a Build Tool for Scala,” 2012, https://github.com/harrah/xsbt/.

3.4. Scala imports

You’ve already seen some examples of import in previous chapters, but I haven’t discussed it. At first glance, the Scala import looks similar to Java imports, and it’s true they’re similar, but Scala adds some coolness to it. To import all the classes under the package com.mongodb, you have to declare the import as follows:

import com.mongodb._

Here’s another use for _, and in this context it means you’re importing all the classes under the com.mongodb package. In Scala, import doesn’t have to be declared at the top of the file; you could use import almost anywhere:

scala> val randomValue = { import scala.util.Random
         new Random().nextInt
      }
randomValue: Int = 1453407425

In this case you’re importing the Random class defined in the scala.util package in the Scala code block, and it’s lexically scoped inside the block and won’t be available outside it. Because the Scala package is automatically imported to all Scala programs, you could rewrite the block by relatively importing the util.Random class:

scala> val randomValue = { import util.Random
     new Random().nextInt
    }
randomValue: Int = 619602925

In Scala, when you import a package, Scala makes its members, including subpackages, available to you. To import members of a class, you have to put ._ after the class name:

scala> import java.lang.System._
import java.lang.System._

scala> nanoTime
res0: Long = 1268518636387441000

Here you’re invoking the nanoTime method defined in the System class without a prefix because you’ve imported the members of the System class. This is similar to static imports in Java (Scala doesn’t have the static keyword). Because imports are relatively loaded, you could import the System class in the following way as well:

scala> import java.lang._
import java.lang._

scala> import System._
import System._

scala> nanoTime
res0: Long = 1268519178151003000

You could also list multiple imports separated by commas. Scala also lets you map a class name to another class name while importing—you’ll see an example of that soon.

The _root_ package in Scala

Consider the following example:

package monads { class IOMonad }
package io {
  package monads {
    class Console { val m = new monads.IOMonad }
  }
}

If you try to compile this code, you’ll get an error saying that type IOMonad isn’t available. That’s because Scala is looking for the IOMonad type in the io.monads package, not in another top-level package called monads. To specify a top-level package you have to use _root_:

val m = new _root_.monads.IOMonad

Another point to note here is that if you create classes or objects without a package declaration, they belong to an empty package. You can’t import an empty package, but the members of an empty package can see each other.

There’s one handy feature of Scala import: it allows you to control the names that you import in your namespace, and in some cases it improves readability. In Java, for example, working with both java.util.Date and java.sql.Date in the same file becomes confusing; in Scala you could easily remap java.sql.Date to solve the problem:

import java.util.Date
import java.sql.{Date => SqlDate}
import RichConsole._
val now = new Date
p(now)
val sqlDate = new SqlDate(now.getTime)
p(sqlDate)

The java.sql.Date is imported as SqlDate to reduce confusion with java.util.Date. You can also hide a class using import with the help of the underscore:

import java.sql.{Date => _ }

The Date class from the java.sql package is no longer visible for use.

To finish the functionality required for the first user story, you still need to add methods for creating and dropping the database. To achieve that you’ll add the methods shown in the following listing.

Listing 3.2. Completed MongoClient
package com.scalainaction.mongo

class MongoClient(val host:String, val port:Int) {
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)

  def version = underlying.getVersion

  def dropDB(name:String) = underlying.dropDatabase(name)

  def createDB(name:String) = DB(underlying.getDB(name))

  def db(name:String) = DB(underlying.getDB(name))
}

Everything in this code should be familiar to you except the createDB and db methods. I haven’t yet introduced DB objects (I do that in the next section). The createDB and db method implementations are identical because the getDB method defined in the Java driver creates a db if one isn’t found, but I wanted to create two separate methods for readability.

3.5. Objects and companion objects

Before I show you the DB class used in the previous example, let’s explore Scala objects. Scala doesn’t provide any static modifier, and that has to do with the design goal of building a pure object-oriented language where every value is an object, every operation is a method call, and every variable is a member of some object. Having static doesn’t fit well with that goal, and along with that there are plenty of downsides[4] to using static in the code. Instead, Scala supports something called singleton objects. A singleton object allows you to restrict the instantiation of a class to one object.[5] Implementing a singleton pattern in Scala is as simple as the following:

4 “Cutting out Static,” Gilad Bracha blog, Room 101, Feb. 17, 2008, http://gbracha.blogspot.com/2008/02/cutting-out-static.html.

5 “Singleton pattern,” Wikipedia, http://en.wikipedia.org/wiki/Singleton_pattern.

object RichConsole {
  def p(x: Any) = println(x)
}

Here RichConsole is a singleton object. The object declaration is similar to a class declaration except instead of class you’re using the object keyword. To invoke the new p method, you have to prefix it with the class name, as you’d invoke static methods in Java or C#:

scala> :l RichConsole.scala
Loading RichConsole.scala...
defined module RichConsole

scala> RichConsole.p("rich console")
rich console

You can import and use all the members of the RichConsole object as follows:

scala> import RichConsole._
import RichConsole._

scala> p("this is cool")
this is cool

The DB object introduced in listing 3.2 is nothing but a factory to create DB instances representing a database in MongoDB:

object DB {
  def apply(underlying: MongDB) = new DB(underlying)
}

What’s interesting here is that when you use a DB object as a factory, you’re calling it as if it’s a function, DB(underlying.getDB(name)), whereas you’d expect something like DB.apply(underlying.getDB(name)). Scala provides syntactic sugar that allows you to use objects as function calls. Scala achieves this by translating these calls into the apply method, which matches the given parameters defined in the object or class. If there’s no matching apply method, it will result in a compilation error. Even though calling an apply method explicitly is valid, a more common practice is the one I’m using in the example. Note also that an object is always evaluated lazily, which means that an object will be created when its first member is accessed. In this case, it’s apply.

The Factory pattern in Scala

When discussing constructors I mentioned that sometimes working with constructors could create some limitations like processing or validating parameters because in overloaded constructors the first line has to be a call to another constructor or the primary constructor. Using Scala objects we could easily address that problem because the apply method has no such limitation. For example, let’s implement a Factory pattern in Scala. Here you’ll create multiple Role classes, and based on the role name you’ll create an appropriate role instance:

abstract class Role { def canAccess(page: String): Boolean }
class Root extends Role {
  override def canAccess(page:String) = true
}
class SuperAnalyst extends Role {
  override def canAccess(page:String) = page != "Admin"
}
class Analyst extends Role {
override def canAccess(page:String) = false }
object Role {
  def apply(roleName:String) = roleName match {
    case "root" => new Root
    case "superAnalyst" => new SuperAnalyst
    case "analyst" => new Analyst
  }
}

Now you can use the role object as a factory to create instances of various roles:

val root = Role("root")
val analyst = Role("analyst")

Inside the apply method you’re creating an instance of the DB class. In Scala, both a class and an object can share the same name. When an object shares a name with a class, it’s called a companion object, and the class is called a companion class. Now the DB.scala file looks like the following:

package com.scalainaction.mongo
import com.mongodb.{DB => MongoDB}

class DB private(val underlying: MongoDB) {
}

object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}

First, the DB class constructor is marked as private so that nothing other than a companion object can use it. In Scala, companion objects can access private members of the companion class, which otherwise aren’t accessible to anything outside the class. In the example, this might look like overkill, but there are times when creating an instance of classes through a companion object is helpful (look at the sidebar for the factory pattern). The second interesting thing in the previous code is the mongodb import statement. Because of the name conflict, you’re remapping the DB class defined by the Java driver to MongoDB.

Package object

The only things you can put in a package are classes, traits, and objects. But with the help of the package object, you can put any kind of definition in a package, such as a class. For example, you can add a helper method in a package object that will be available to all members of the package. Each package is allowed to have one package object. Normally you would put your package object in a separate file, called package.scala, in the package that it corresponds to. You can also use the nested package syntax, but that’s unusual:

package object bar {
  val minimumAge = 18
  def verifyAge = {}
}

minimumAge and verifyAge will be available to all members of the package bar. The following example uses verifyAge defined inside the package object:

package bar
class BarTender {
    def serveDrinks = { verifyAge; ... }
}

The main use case for package objects is when you need definitions in various places inside your package, as well as outside the package when you use the API defined by the package.

In MongoDB, a database is divided into multiple collections of documents. Shortly you’ll see how you can create a new collection inside a database, but for now add a method to retrieve all the collection names to the DB class:

package com.scalainaction.mongo
import com.mongodb.{DB => MongoDB}
import scala.collection.convert.Wrappers._

class DB private(val underlying: MongoDB) {
    def collectionNames =  for(name <- new
        JSetWrapper(underlying.getCollectionNames)) yield name
}

The only thing that looks somewhat new is the Wrappers object. You’re using utility objects provided by Wrappers to convert a java.util.Set to a Scala set so you can use a Scala for-comprehension. Wrappers provides conversions between Scala and Java collections. To try out the mongodb driver, write this sample client code:

import com.scalainaction.mongo._

def client = new MongoClient
def db = client.createDB("mydb")

for(name <- db.collectionNames) println(name)

This sample client creates a database called mydb and prints the names of all the collections under the database. If you run the code, it will print test and system.indexes, because by default MongoDB creates these two collections for you.

Now you’re going to expose CRUD (create, read, update, delete) operations in the Scala driver so that users of your driver can work with documents. The following listing shows the Scala driver code you’ve written so far.

Listing 3.3. Completed MongoClient.scala
package com.scalainaction.mongo

import com.mongodb._

class MongoClient(val host:String, val port:Int) {
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)

  def version = underlying.getVersion
  def dropDB(name:String) = underlying.dropDatabase(name)
  def createDB(name:String) = DB(underlying.getDB(name))
  def db(name:String) = DB(underlying.getDB(name))
}

Using MongoClient, your driver will be able to connect to the running MongoDB server, to a given host and port, or to the local MongoDB server. You also added methods like dropDB, createDB, and db to manage MongoDB databases. The following listing shows the DB class you created to wrap the underlying MongoDB database.

Listing 3.4. DB.scala
package com.scalainaction.mongo

import com.mongodb.{DB => MongoDB}
import scala.collection.convert.Wrappers._

class DB private(val underlying: MongoDB) {
  def collectionNames =  for(name <- new
    JSetWrapper(underlying.getCollectionNames)) yield name
}

object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}

So far, you haven’t added much functionality to the DB class. The only thing it provides is an easier way to access names of the collections of a given database. But that’s about to change with Scala traits.

3.6. Mixin with Scala traits

A trait is like an abstract class meant to be added to other classes as a mixin. Traits can be used in all contexts where other abstract classes could appear, but only traits can be used as mixin. In OOP languages, a mixin is a class that provides certain functionality that could be used by other classes. You can also view a trait as an interface with implemented methods. You’ll see shortly how Scala traits will help you in implementing the second user story.

Note

Another difference between traits and abstract classes in Scala is that an abstract class can have constructor parameters, but traits can’t take any parameters. Both can take type parameters, which I discuss in the next chapter.

The second user story you need to implement in your driver is an ability to create, delete, and find documents in a MongoDB database. MongoDB stores documents in a collection, and a database could contain multiple collections. You need to create a component that will represent a MongoDB collection. The common use case is to retrieve documents from a collection; another use case would be to perform administrative functions like creating and deleting documents. The Java Mongo driver provides a DBCollection class that exposes all the methods to operate on the collection, but you’re going to take it and slice it into multiple views. In Scala, you could do that using a trait. You’ll use different traits for different types of jobs.

In this implementation you’ll wrap the existing DBCollection and provide three kinds of interfaces: a read-only collection, an administrable collection, and an updatable collection. The following listing shows how the read-only collection interface will look.

Listing 3.5. ReadOnly collection trait
import com.mongodb.{DBCollection => MongoDBCollection }
import com.mongodb.DBObject

trait ReadOnly {

  val underlying: MongoDBCollection

  def name = underlying getName
  def fullName = underlying getFullName
  def find(doc: DBObject) = underlying find doc
  def findOne(doc: DBObject) = underlying findOne doc
  def findOne = underlying findOne
  def getCount(doc: DBObject) = underlying getCount doc
}

The only abstract member defined in this trait is underlying, which is an abstract value. In Scala, it’s possible to declare abstract fields like abstract methods that need to be inherited by subclasses.

Note

The difference between def and val is that val gets evaluated when an object is created, but def is evaluated every time a method is called.

It’s not necessary to have an abstract member in a trait, but usually traits contain one or more abstract members. Note that you’re invoking the findOne or getCount method on the underlying collection without using the . operator. Scala allows you to treat any method as you would the infix operator (+, -, and so on).

The DBObject parameter is nothing but a key-value map provided by the Mongo Java driver, and you’re going to use the class directly. In the full-blown driver implementation, you’ll probably want to wrap that class too, but for the toy driver you can live with this bit of leaky abstraction. I’ll talk about the details of these methods shortly when you test the methods.

The next two traits you’re going to look at are Administrable and Updatable. In the Administrable trait, you’ll expose methods for drop collection and indexes; and in the Updatable trait you’ll allow create and remove operations on documents—see the following listing.

Listing 3.6. Administrable and Updatable traits
trait Administrable extends ReadOnly {
  def drop: Unit = underlying drop
  def dropIndexes: Unit = underlying dropIndexes
}

trait Updatable extends ReadOnly {
  def -=(doc: DBObject): Unit = underlying remove doc
  def +=(doc: DBObject): Unit = underlying save doc
}

Both traits extend the ReadOnly trait because you also want to provide all the features of a read-only collection. If your trait extends another trait or class, then that trait can only be mixed into a class that also extends the same superclass or trait. This makes sense because you want to make sure that someone else implements the abstract members that your trait depends on. As with abstract classes, you can’t create an instance of a trait; you need to mix it with other concrete classes. Here’s a concrete implementation of the read-only collection:

class DBCollection(override val underlying: MongoDBCollection)
                   extends ReadOnly

You’re overriding the underlying abstract value with whatever value will be passed to the primary constructor when creating the instance of DBCollection. Note that the override modifier is mandatory when overriding members of a superclass. The following adds three methods that return different flavors of the collection:

private def collection(name: String) = underlying.getCollection(name)

def readOnlyCollection(name: String) = new DBCollection(collection(name))
def administrableCollection(name: String) = new
                    DBCollection(collection(name)) with Administrable
def updatableCollection(name: String) = new
                    DBCollection(collection(name)) with Updatable

Here you’re getting the underlying collection by name and wrapping it into a DBCollection instance. When building the administrable and updatable collection, you’re mixing in the corresponding traits using a with clause. Using the with keyword, you can mix one or more traits into an existing concrete class. Another way of thinking about Scala mixins is as decorators. Like decorators, mixins add more functionality to existing classes. That allows you to widen a thin interface with additional traits when needed, as you did with the ReadOnly, Administrable, and Updatable traits. The next two listings show what the DB class (listing 3.7) and DBCollection class (listing 3.8) look like so far.

Listing 3.7. Completed DB.scala
package com.scalainaction.mongo

import com.mongodb.{DB => MongoDB}
import scala.collection.convert.Wrappers._

class DB private(val underlying: MongoDB) {
  private def collection(name: String) = underlying.getCollection(name)

  def readOnlyCollection(name: String) = new DBCollection(collection(name))
  def administrableCollection(name: String) = new
    DBCollection(collection(name)) with Administrable
  def updatableCollection(name: String) = new
    DBCollection(collection(name)) with Updatable
  def collectionNames = for(name <- new
    JSetWrapper(underlying.getCollectionNames)) yield name
}

object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}
Listing 3.8. DBCollection.scala

If you’ve done any Ruby programming, you’ll find lots of similarity with Ruby modules. One advantage of traits compared to module systems available in other languages is that the trait mixin is checked at compile time. If you make mistakes while stacking traits, the compiler will complain.

Now you’ll build a client to demonstrate that the driver works. Ideally, you should always write unit tests to make sure your code works. Chapter 8 explores testing in Scala land. For now, the following listing shows the small client that validates your driver.

Listing 3.9. Test client for driver QuickTour.scala

In the test client you’re creating collections using the methods exposed by the DB class. You’re using BasicDBObject provided by the underlying MongoDB driver to test the find method. BasicDBObject is nothing but a wrapper around a Java map. MongoDB being a schema-free database, you can put any key-value pair on it and save it to the database . At the end of the test, you’re using the same BasicDBObject to query the database .

To run the test client, make sure you have the Mongo Java driver .jar file in the classpath. To specify the classpath to the Scala interpreter, use the –cp option.

After the release of your driver, all the users are happy. But it turns out that the driver is slow in fetching documents, and users are asking whether there’s any way we could improve the performance. One way to solve this problem immediately is by memoization.[6] To speed things up, you’ll remember the calls made to the find method and avoid making the same call to the underlying collection again. The easiest way to implement the solution is to create another trait and mix it in with the other traits. By nature Scala traits are stackable, meaning one trait can modify or decorate the behavior of another trait down the stack. Here’s how to implement the Memoizer trait:

6 “Memoization,” Wikipedia, http://en.wikipedia.org/wiki/Memoization.

trait Memoizer extends ReadOnly {
  val history = scala.collection.mutable.Map[Int, DBObject]()
  override def findOne = {
    history.getOrElseUpdate(-1, { super.findOne })
  }
  override def findOne(doc: DBObject) = {
    history.getOrElseUpdate(doc.hashCode, { super.findOne(doc) })
  }
}

You’re keeping track of all the resulting DBObjects, and when the same request is made a second time, you’re not going to make a call to MongoDB—instead, you’ll return from the map. The getOrElseUpdate method is interesting; it allows you to get the value for the given key, and if it doesn’t exist, it invokes the function provided in the second parameter. Then it stores the value with the key in the map and returns the result. You saved a complete if and else block with a single method. In the case of the parameterless findOne method, you’re using -1 as the key because the method doesn’t take a parameter. To use this memoizer trait, you have to modify the existing DB class as follows:

def readOnlyCollection(name: String) =
    new DBCollection(collection(name)) with Memoizer
def administrableCollection(name: String) =
    new DBCollection(collection(name)) with Administrable with Memoizer
def updatableCollection(name: String) =
    new DBCollection(collection(name)) with Updatable with Memoizer

Now whenever the findOne method is invoked, the overridden version will be called, and the result will be cached.

There’s a little problem with this Memoizer approach, though. If you remove documents from a collection, the Memoizer will still have them and return them. You could solve this by extending the UpdatableCollection trait and overriding the remove method. The next section discusses how stackable traits are implemented in Scala.

3.6.1. Class linearization

If you’ve worked with C++ or Common Lisp, then the mixin of traits will look like multiple inheritance. The next question is how Scala handles the infamous diamond problem (http://en.wikipedia.org/wiki/Diamond_problem). See figure 3.1. Before I answer that question, let’s see how your hierarchy will look if you have a diamond problem for the following UpdatableCollection:

Figure 3.1. Class hierarchy of UpdatableCollection before class linearization

Class UpdatableCollection
    extends DBCollection(collection(name)) with Updatable

The problem with this hierarchy is that trying to invoke one of the find methods on UpdatableCollection will result in an ambiguous call because you could reach the ReadOnly trait from two different paths. Scala solves this problem using a something called class linearization. Linearization specifies a single linear path for all the ancestors of a class, including both the regular superclass chain and the traits. This is a two-step process in which it resolves method invocation by first using right-first, depth-first search and then removing all but the last occurrence of each class in the hierarchy. Let’s look at this in more detail. First, all classes in Scala extend scala.AnyRef, which in turn inherits from the scala.Any class. (I explain Scala class hierarchy later in this chapter.) The linearization of the ReadOnly trait is simple because it doesn’t involve multiple inheritance:

ReadOnly –> AnyRef –> Any

Similarly, Updatable and DBCollection also don’t have that issue:

Updatable –> ReadOnly –> AnyRef –> Any
DBCollection –> ReadOnly –> AnyRef –> Any

When class linearization is applied to your UpdatableCollection, it puts the trait first after the class because it’s the rightmost element and then removes duplication. After linearization, your UpdatableCollection looks like the following:

UpdatableCollection –> Updatable –> DBCollection –> ReadOnly –> AnyRef –> Any

Now if you add the Memoizer trait into the mix, it will show up before Updatable because it’s the rightmost element:

UpdatableCollection –> Memoizer –> Updatable –> DBCollection –> ReadOnly –>
     AnyRef –> Any

Figure 3.2 illustrates how classes and traits are laid out for the UpdatableCollection class. The figure shows traits in a separate place because I want you to think differently about them. When traits have methods implemented, they work as a façade. Check the sidebar “Trait class files on JVM” for more details. The dotted lines show the hierarchy, and the solid lines with arrowheads show how methods will be resolved after linearization.

Figure 3.2. Class linearization of UpdatableCollection

Trait class files on JVM

Depending on how you define a trait, the Scala compiler generates class files differently. When you define a trait with only a method declaration and without any method body, it produces a Java interface. You could use javap –c <class file name> to inspect class files generated by Scala. For example, trait Empty { def e: Int } will produce the following class file:

public interface Empty{
    public abstract int e();
}

When a trait declares concrete methods or code, Scala generates two class files: one for the interface (as shown in the previous code) and a new class file that contains the code. When a class extends a trait, the variables declared in the trait are copied to the class file, and the method defined in the trait becomes a façade method in the class. These façade methods in the class will call the methods defined in the trait code class.

3.6.2. Stackable traits

You’ve seen multiple uses for Scala traits. To recap, you’ve used a Scala trait as an interface using ReadOnly. You’ve used it as a decorator to expand the functionality of DBCollection using the Updatable and Administrable traits. And you’ve used traits as a stack where you’ve overridden the functionality of a ReadOnly trait with Memoizer. The stackable feature of a trait is useful when it comes to modifying the behavior of existing components or building reusable components. Chapter 7 explores abstractions provided by Scala in building reusable components. For now, let’s look at another example and explore stackable traits in more detail.

You have another requirement for your driver; this time it’s related to locale. The Scala Mongo driver is so successful that it’s now used internationally. But the documents that you’re returning aren’t locale-aware. The requirement is to make your read-only interface locale-aware. Luckily, all the non-English documents have a field called locale. Now if only you could change your find to use that, you could address this problem.

You could change your find method in the ReadOnly trait to find by locale, but that would break all your users looking for English documents. If you build another trait and mix it with ReadOnly, you could create a new kind of Collection that will find documents using locale:

trait LocaleAware extends ReadOnly {
  override def findOne(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.findOne(doc)
  }

  override def find(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.find(doc)
  }
}

Now when creating a new Collection, you could mix in this trait:

new DBCollection(collection(name)) with Memoizer with LocaleAware

The traits could be reordered as follows, with the same result:

new DBCollection(collection(name)) with LocaleAware with Memoizer

As you can see, it’s easy to use traits in a stack to add or modify the behavior of existing classes or traits. This kind of use is common in Scala code bases, and you’ll see more on them throughout the second part of the book. Before we leave traits, there’s one more thing I’d like to mention: the use of super. As you can see, when creating a trait you can’t tell how your trait will get used and who will be above you. All you know is that it has to be of a type that your trait extends. In the previous code, you could mix in the LocaleAware trait before or after Memoizer, and in each case super would mean something different. The interpretation of super in traits is dynamically resolved in Scala.

ScalaObject trait

When discussing class linearization, I didn’t give you the complete picture. Scala always inserts a trait called scala.ScalaObject as a last mixin in all the classes you create in Scala. The complete linearization of UpdatableCollection is as follows:

UpdatableCollection -> Memoizer -> Updatable -> DBCollection ->
         ReadOnly -> ScalaObject -> AnyRef -> Any

Prior to Scala 2.8, ScalaObject used to provide methods like $tag to help with pattern matching, but from Scala 2.8 on, the ScalaObject trait is a marker (empty) trait.

3.7. Case class

Case classes are a special kind of class created using the keyword case. When the Scala compiler sees a case class, it automatically generates boilerplate code so you don’t have to do it. Here’s an example of a Person class:

scala> case class Person(firstName:String, lastName:String)
defined class Person

In this code example, you’re creating a Person case class with firstName and lastName parameters. But when you prefix a class with case, the following things will happen automatically:

  • Scala prefixes all the parameters with val, and that will make them public value. But remember that you still never access the value directly; you always access through accessors.
  • Both equals and hashCode are implemented for you based on the given parameters.
  • The compiler implements the toString method that returns the class name and its parameters.
  • Every case class has a method named copy that allows you to easily create a modified copy of the class’s instance. You’ll learn about this later in this chapter.
  • A companion object is created with the appropriate apply method, which takes the same arguments as declared in the class.
  • The compiler adds a method called unapply, which allows the class name to be used as an extractor for pattern matching (more on this later).
  • A default implementation is provided for serialization:
scala> val me = Person("Nilanjan", "Raychaudhuri")
me: Person = Person(Nilanjan,Raychaudhuri)

scala> val myself = Person("Nilanjan", "Raychaudhuri")
myself: Person = Person(Nilanjan,Raychaudhuri)

scala> me.equals(myself)
res1: Boolean = true

scala> me.hashCode
res2: Int = 1688656232
scala> myself.hashCode
res4: Int = 1688656232

Now think about how many times you’ve created a data transfer object (DTO) with only accessors for the purpose of wrapping some data. Scala’s case classes will make that easier for you the next time. Both equals and hashCode implementations also make it safer to use with collections.

Note

You’re allowed to prefix the parameters to the case class with var if you want both accessors and mutators. Scala defaults it to val because it encourages immutability.

Like any other class, a case class can extend other classes, including trait and case classes. When you declare an abstract case class, Scala won’t generate the apply method in the companion object. That makes sense because you can’t create an instance of an abstract class. You can also create case objects that are singleton and serializable:

trait Boolean
case object Yes extends Boolean
case object No extends Boolean

Scala case classes and objects make it easy to send serializable messages over the network. You’ll see a lot of them when you learn about Scala actors.

Note

From Scala 2.8 on, case classes without a parameter list are deprecated. If you have a need, you can declare your case class without a parameter. Use () as a parameter list or use the case object.

Let’s put your recently gained knowledge of case classes to use in the MongoDB driver. So far, you’ve implemented basic find methods in your driver. It’s great, but you could do one more thing to the driver to make it more useful. MongoDB supports multiple query options like Sort, Skip, and Limit that you don’t support in your driver. Using case classes and a little pattern matching, you could do this easily. You’ll add a new finder method to the collection to find by query and query options. But first, let’s define the query options you’re going to support:

sealed trait QueryOption

case object NoOption extends QueryOption

case class Sort(sorting: DBObject, anotherOption: QueryOption)
    extends QueryOption

case class Skip(number: Int, anotherOption: QueryOption)
    extends QueryOption

case class Limit(limit: Int, anotherOption: QueryOption)
    extends QueryOption

Here you’re creating four options: Sort, Skip, Limit, and NoOption. The NoOption case is used when no option is provided for the query. Each query option could have another query option because you’ll support multiple query options at the same time. The Sort option takes another DBObject in which users can specify sorting criteria.

Note that all the option case classes extend an empty trait, and it’s marked as sealed. I’ll talk about modifiers in detail later in the chapter, but for now a sealed modifier stops everyone from extending the trait, with a small exception. To extend a sealed trait, all the classes need to be in the same source file. In this case, I’ve defined all the previous classes in the DBCollection.scala file.

For the Query class, you’ll wrap your good old friend DBObject and expose methods like sort, skip, and limit so that users can specify query options:

case class Query(q: DBObject, option: QueryOption = NoOption) {
  def sort(sorting: DBObject) = Query(q, Sort(sorting, option))
  def skip(skip: Int) = Query(q, Skip(skip, option))
  def limit(limit: Int) = Query(q, Limit(limit, option))
}

Here each method creates a new instance of a query object with an appropriate query option so that, like a fluent interface (http://martinfowler.com/bliki/FluentInterface.html), you can chain the methods together as in the following:

var rangeQuery = new BasicDBObject("i", new BasicDBObject("$gt", 20))
var richQuery = Query(rangeQuery).skip(20).limit(10)

Here you’re searching documents for which the i > 20 condition is true. From the result set you skip 20 documents and limit your result set to 10 documents. The most extraordinary part of the code is the last parameter of the Query class: option: QueryOption = NoOption. Here you’re assigning a default value to the parameter so that when the second parameter isn’t specified, as in the previous snippet, the default value will be used. You’ll look at default parameters in the next section. I’m sure that, as a focused reader, you’ve already spotted the use of the companion object that Scala generates for case classes. When creating an instance of a case class, you don’t have to use new because of the companion object. To use the new query class, add the following new method to the ReadOnly trait:

def find (query: Query) = { "..." }

Before discussing implementation of the find-by-query method, let’s see how case classes help in pattern matching. You’ll be using pattern matching to implement the method.

You learned about pattern matching in chapter 2, but I haven’t discussed case classes and how they could be used with pattern matching. One of the most common reasons for creating case classes is the pattern-matching feature that comes free with case classes. Let’s take the Person case class once again, but this time you’ll extract firstName and lastName from the object using pattern matching:

scala> case class Person(firstName:String, lastName: String)
defined class Person

scala> val p = Person("Matt", "vanvleet")
p: Person = Person(Matt,vanvleet)

scala> p match {

        case Person(first, last) => println(">>>> " + first + ", " + last)
      }
>>>> Matt, vanvleet

Look how you extracted the first and last names from the object using pattern matching. The case clause should be familiar to you; here you’re using a variable pattern in which the matching values get assigned to the first and last variables. Under the hood, Scala handles this pattern matching using a method called unapply. If you have to hand-code the companion object that gets generated for Person, it will look like following:

object Person {

  def apply(firstName:String, lastName:String) = {
    new Person(firstName, lastName)
  }
  def unapply(p:Person): Option[(String, String)] =
    Some((p.firstName, p.lastName))
}

The apply method is simple; it returns an instance of the Person class and it is called when you create an instance of a case class. The unapply method gets called when the case instance is used for pattern matching. Typically, the unapply method is supposed to unwrap the case instance and return the elements (parameters used to create the instance) of the case class. I’ll talk about the Option type in Scala in detail in the next chapter, but for now think of it as a container that holds a value. If a case class has one element, the Option container holds that value. But because you have more than one, you have to return a tuple of two elements.

Note

Sometimes instead of unapply, another method called unapplySeq could get generated if the case class parameters end with a repeated parameter (variable argument). I’ll discuss that in a later chapter.

In the discussion of for-comprehensions in chapter 2, I didn’t mention that the generator part of for-comprehensions uses pattern matching. I can best describe this with an example. Here you’re creating a list of persons and looping through them using pattern matching:

scala> val people = List(
     | Person("Simon", "kish"),
     | Person("Phil", "Marzullo"),
     | Person("Eric", "Weimer")
     | )
people: List[Person] = List(Person(Simon,kish), Person(Phil,Marzullo),
     Person(Eric,Weimer))

scala> for(Person(first, last) <- people) yield first + "," + last
res12: List[java.lang.String] =
    List(Simon,kish, Phil,Marzullo, Eric,Weimer)

You’ll see more examples of extractors and pattern matching throughout the book. Before we leave this section, I still owe you the implementation of the find-by-query method, so here you go (see the following listing).

Listing 3.10. ReadOnly trait

Here you’re using pattern matching to apply each query option to the result returned by the find method—in this case, DBCursor. The nested applyOptions function is applied recursively because each query option could wrap another query option identified by the next variable, and you bail out when it matches NoOption.

When it comes to overload methods (methods with the same name), you have to specify the return type; otherwise, the code won’t compile. You have a similar limitation for recursive method calls. Scala type inference can’t infer the type of recursive methods or functions. In case of type errors, it’s always helpful to add type information. Using the test client in the following listing, you could test your new finder method.

Listing 3.11. TestFindByQuery.scala

When you run this client, you’ll see output similar to the following:

{ "_id" : "4ba0df2c2771d753375f4aa7" , "i" : 41}
{ "_id" : "4ba0df2c2771d753385f4aa7" , "i" : 42}
{ "_id" : "4ba0df2c2771d753395f4aa7" , "i" : 43}
{ "_id" : "4ba0df2c2771d7533a5f4aa7" , "i" : 44}
{ "_id" : "4ba0df2c2771d7533b5f4aa7" , "i" : 45}
{ "_id" : "4ba0df2c2771d7533c5f4aa7" , "i" : 46}
{ "_id" : "4ba0df2c2771d7533d5f4aa7" , "i" : 47}
{ "_id" : "4ba0df2c2771d7533e5f4aa7" , "i" : 48}
{ "_id" : "4ba0df2c2771d7533f5f4aa7" , "i" : 49}
{ "_id" : "4ba0df2c2771d753405f4aa7" , "i" : 50}

The id values in the output might be different for you because they’re autogenerated by MongoDB.

Common arguments against pattern matching

Pattern matching is common in functional programming languages, but not in the world of OOP languages. Common arguments against pattern matching by object-oriented developers are that pattern matching could be replaced by a Visitor pattern, pattern matching isn’t extensible, and pattern matching breaks encapsulation.

First, pattern matching reduces lots of boilerplate code when compared to the Visitor pattern. The extensibility argument enters the picture when pattern matching is supported only for basic datatypes like Int, Long, or String. But Scala takes pattern matching much further and beyond basic datatypes with case classes. Pattern matching implemented for case classes matches only the constructor parameters provided for the case classes. This way, you don’t have to expose hidden fields of the class, and you ensure encapsulation.

3.8. Named and default arguments and copy constructors

Scala lets you specify method arguments using a named style. When you have methods or class constructors taking similar types of arguments, it becomes difficult to detect errors if you swap them by mistake. Let’s take the example of Person again. Instead of passing in an order of first name, last name, if we swap the order, Scala won’t complain:

scala> case class Person(firstName:String, lastName:String)
defined class Person

scala> val p = Person("lastname", "firstname")
p: Person = Person(lastname,firstname)

Unfortunately, both parameters are of the same type, and the compiler can’t detect the mistake at compile time. But now, using named style arguments, you can avoid the problem:

scala> val p = Person(lastName = "lastname", firstName = "firstname")
p: Person = Person(firstname,lastname)

The named arguments use the same syntax as a variable assignment, and when you use named arguments, the order doesn’t matter. You can mix the named arguments with positional arguments, but that’s usually a bad idea. When going for named arguments, always try to use a named style for all the arguments. The following example uses a named style for the first argument but not for the second. As mentioned earlier, it’s good practice to avoid this:

scala> val p = Person(firstName = "firstname", "lastname")
p: Person = Person(firstname,lastname)

When using a named style, if the parameter name doesn’t match, the Scala compiler will complain about the value not being found. But when you override a method from a superclass, the parameters’ names don’t have to match the names in the superclass method. In this case, the static type of the method determines which names have to be used. Consider this example, where you have the Person trait and SalesPerson overriding the grade method and changing the parameter name in the process from years to yrs:

scala> trait Person { def grade(years: Int): String }
defined trait Person
scala> class SalesPerson extends Person {  def grade(yrs: Int) = "Senior" }
defined class SalesPerson

scala> val s = new SalesPerson
s: SalesPerson = SalesPerson@42a6cdf5

scala> s.grade(yrs=1)
res17: java.lang.String = Senior

scala> s.grade(years=1)
<console>:12: error: not found: value years
       s.grade(years=1)
               ^

Here years won’t work because the type of the s instance is SalesPerson. If you force the type variable to Person, then you can use years as a named argument. I know this is a little tricky to remember, so watch out for errors like this:

scala> val s: Person = new SalesPerson
s: Person = SalesPerson@5418f143

scala> s.grade(years=1)
res19: String = Senior

The value of the named argument could be an expression like a method or block of code, and every time the method is called, the expression is evaluated:

scala> s.grade(years={val x = 10; x + 1})
res20: String = Senior

The complementing feature to named arguments is default arguments. You’ve already seen one example of a default argument in the query example, where the last argument of the case class defaulted to NoOption:

case class Query(q: DBObject, option: QueryOption = NoOption) {
  def sort(sorting: DBObject) = Query(q, Sort(sorting, option))
  def skip(skip: Int) = Query(q, Skip(skip, option))
  def limit(limit: Int) = Query(q, Limit(limit, option))
}

The default argument has the form arg: Type = expression, and the expression part is evaluated every time the method uses the default parameter. If you create a Query instance using Skip, the default won’t be used:

val skipOption = Skip (10, NoOption)
val newQuery = Query(new BasicDBObject(), skipOption)

One of the interesting uses of default arguments in Scala is in the copy method of case classes. Starting from Scala 2.8 on, along with the usual goodies, every case class has an additional method called copy to create a modified instance of the class. This method isn’t generated if any member exists with the same name in the class or in one of its parent classes. The following example uses the copy method to create another instance of the skip query option, but with a Limit option instead of NoOption:

scala> val skipOption = Skip(10, NoOption)
skipOption: Skip = Skip(10,NoOption())

scala> val skipWithLimit = skipOption.copy(anotherOption = Limit(10,
                               NoOption))
skipWithLimit: Skip = Skip(10,Limit(10,NoOption))

The copy method is using a named argument to specify the parameter that you’d like to change. The copy method generated by the Scala compiler for the Skip case class looks like the following:

case class Skip(number: Int, anotherOption: QueryOption)
        extends QueryOption {
  def copy(number: Int = number,
        anotherOption: QueryOption = anotherOption) = {
    Skip(number, anotherOption)
  }
}

As you can see, in the generated method all the parameters are defaulted to the value provided to the constructor of the class, and you can pick and choose the parameter value you want to change during the copy. If no parameters are specified, copy will create another instance with the same values:

scala> Skip(10, NoOption) == Skip(10, NoOption).copy()
res22: Boolean = true

In Scala, invoking the == method is the same as calling the equals method. The == method is defined in the scala.Any class, which is the parent class for all classes in Scala.

3.9. Modifiers

You’ve already seen a few modifiers in action, but let’s look deeper into them. Along with standard modifiers like private and protected, Scala has more modifiers and new abilities.

The private modifier can be used with any definition, which means it’s only accessible in an enclosed class, its companion object, or a companion class. In Scala, you can qualify a modifier with a class or a package name. In the following example, the private modifier is qualified by class and package name:

package outerpkg.innerpkg

class Outer {
  class Inner {
    private[Outer] def f()  = "This is f"
    private[innerpkg] def g() = "This is g"
    private[outerpkg] def h() = "This is h"
  }
}

Here, access to the f method can appear anywhere within the Outer class, but not outside it. The method g is accessible anywhere within outerpkg.innerpkg. It’s like the package private in Java. You could use it to make your methods available for unit tests in the same package. Because the h method is qualified with outerpkg, it can appear anywhere within outerpkg and its subpackages.

Scala also lets you qualify the private modifier with this: private[this]. In this case, it means object private. And object private is only accessible to the object in which it’s defined. When members are marked with private without a qualifier, they’re called class-private.

The protected modifier is applicable to class member definitions. It’s accessible to the defining class and its subclasses. It’s also accessible to a companion object of the defining class and companion objects of all the subclasses. Like the private modifier, you can also qualify the protected modifier with class, package, and this. By default, when you don’t specify any modifier, everything is public. Scala doesn’t provide any modifier to mark members as public.

Like Java, Scala also provides an override modifier, but the main difference is that in Scala the override modifier is mandatory when you override a concrete member definition from the parent class. The override modifier can be combined with an abstract modifier, and the combination is allowed only for members of traits. This modifier means that the member in question must be mixed with a class that provides the concrete implementation. An example will demonstrate this fact. The following creates a DogMood trait (dogs are moody, you know) with an abstract greet method and an AngryMood trait that overrides it:

trait DogMood {
  def greet
}

trait AngryMood extends DogMood {
 override def greet = {
    println("bark")
    super.greet
  }
}

The problem with this code is the super.greet line. You can’t invoke the super greet method because it’s abstract. But super calls are important if you want your trait to be stackable so that it can get mixed in with other traits. In cases like this, you can mark the method with abstract override, which means it should be mixed in with some class that has the concrete definition of the greet method. To make this code compile, you have to add abstract along with override, as in the following:

trait AngryMood extends DogMood {
  abstract override def greet = {
    println("bark")
    super.greet
  }
}

Scala has introduced a new modifier called sealed, which applies only to class definitions. It’s a little different from the final modifier; classes marked final in Scala can’t be overridden by subclasses. But classes marked sealed can be overridden as long as the subclasses belong to the same source file. You used sealed in a previous section when you created QueryOption case classes:

sealed trait QueryOption

This is a common pattern when you want to create a defined set of subclasses but don’t want others to subclass it.

3.10. Value classes: objects on a diet

Starting with version 2.10, Scala allows user-defined value classes (which could be case classes as well) that extend AnyVal. Value classes are a new mechanism to avoid runtime allocation of the objects. To create a value class you need to abide by some important rules, including:

  • The class must have exactly one val parameter (vars are not allowed).
  • The parameter type may not be a value class.
  • The class can not have any auxiliary constructors.
  • The class can only have def members, no vals or vars.
  • The class cannot extend any traits, only universal traits (we will see them shortly).

These are big constraints, so why bother? Value classes allow you to add extension methods to a type without the runtime overhead of creating instances. Look at the following example:

class Wrapper(val name: String) extends AnyVal {
def up() = name.toUpperCase
}

Here Wrapper is a custom value class that wraps the name parameter and exposes an up() method. To invoke the up method create the instance of the Wrapper class as in the following:

val w = new Wrapper("hey")
w.up()

This is only true at compile time. At runtime the expression will be optimized to the equivalent of a method class on a static object: Wrapper.up$extension("hey"). So what is going on? Behind the scenes the Scala compiler has generated a companion object for the value class and rerouted the w.up() calls to the up$extension method in the companion object. The "$extension" is the suffix added to all the methods extracted from the companion class. The contents of the up$extension method are the same as the up() method except all the references to name are changed to use the parameter. Here is an equivalent implementation of a Wrapper companion object:

object Wrapper {
 def up$extension(_name: String) = _name.toUpperCase
}

A value class can only extend a universal trait, one that extends Any (normally traits by default extend AnyRef). Universal traits can only have def members and no initialization code:

trait Printable extends Any {
 def p() = println(this)
}

case class Wrapper(val name: String) extends AnyVal with Printable {
 def up() = name.toUpperCase
}
...
val w = Wrapper("Hey")
w.p()

Even though now you can invoke the p method on a Wrapper instance at runtime an instance will also be created because the implementation of the p method prints the type. There are limitations when allocation is necessary; if you assign a value class to an array, the optimization will fail.

Nonetheless this is a very nice way to add extension methods to an existing type. We will see examples of value classes later in the book. For now let’s explore the rest of the Scala type hierarchy.

3.11. Implicit conversion with implicit classes

Implicit conversion is a method that takes one type of parameter and returns another type. Here is an example of a conversion from Double to Int:

scala> val someInt: Int = 2.3
<console>:7: error: type mismatch;
 found   : Double(2.3)
 required: Int
       val someInt: Int = 2.3
                          ^

scala> def double2Int(d: Double): Int = d.toInt
double2Int: (d: Double)Int
scala> val someInt: Int = double2Int(2.3)
someInt: Int = 2

Usually you cannot assign a Double value to an Int type, but here we are explicitly converting Double to Int using the double2Int method before then assigning it to someInt. We can make the conversion implicit by using the implicit keyword:

implicit double2Int(d: Double): Int = d.toInt

The advantage of implicit conversion is that the compiler will find the appropriate implicit conversion and invoke it for you:

scala> val someInt: Int = 2.3
<console>:7: error: type mismatch;
 found   : Double(2.3)
 required: Int
       val someInt: Int = 2.3
                          ^

scala> implicit def double2Int(d: Double): Int = d.toInt
warning: there were 1 feature warnings; re-run with -feature for details
double2Int: (d: Double)Int

scala> val someInt: Int = 2.3
someInt: Int = 2

What is going on here? The first time we assigned a Double value to an Int type variable it failed, but it succeeds the second time. When the compiler encounters a type error, it doesn’t immediately give up; instead, it looks for any implicit conversions that might fix the error. In this case, double2Int is used to convert Double to Int. The last line will be rewritten by the compiler:

val someInt: Int = double2Int(2.3)

This conversion happens at compile time, and, if no appropriate conversion method is found, the compiler throws a compilation error. The compiler will also throw an error if there is ambiguity in an implicit resolution; for example, more than one implicit conversion is found that matches the given criteria. It is quite safe compared to the runtime extension available in some dynamically typed languages. For now, ignore the warning. We will learn more about this later.

One of the common uses of implicit conversion is to add extension methods to existing types. For example, we know we can create a range by using the to method:

val oneTo10 = 1 to 10

But what if we want to create a range of numbers using the --> method?

val oneTo10 = 1 --> 10

This will fail because there is no --> method defined for Int. We can easily fix this by following two simple steps:

  • Create a type that has a --> method defined for the Int type
  • Provide an implicit conversion

Let’s create a class that defines a --> method for Int and creates a Range of integers:

scala> class RangeMaker(left: Int) {
     |   def -->(right: Int) = left to right
     | }
defined class RangeMaker

scala> val range: Range = new RangeMaker(1).-->(10)
range: Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Here the left operand becomes the constructor parameter and the right operand the parameter to the --> method. The implicit conversion method takes Int and returns RangeMaker:

scala> implicit def int2RangeMaker(left: Int): Range = new RangeMaker(left)

To understand why we designed our type this way, we need to understand how the compiler will parse the 1 --> 10 expression. By default, the Scala compiler always evaluates expressions from left to right. So the expression is translated to 1.-->(10). Since there is no --> method defined for Int, the Scala compiler will look for an implicit conversion that can convert Int to some type that defines the --> method. In this case, the compiler will use the int2RangeMaker method by passing 1 as a parameter, then 10 as a parameter, to the --> method. After the implicit conversion, the 1 --> 10 expression will be converted to int2RangeMaker(1).-->(10).

Since implicit conversion is so commonly used by libraries and applications, Scala provides implicit classes. Implicit classes reduce boilerplate code by combining the steps required for implicit conversion. We can combine the RangeMaker and conversion methods by making the class implicit:

implicit class RangeMaker(left: Int) {
  def -->(right: Int): Range = left to right
}

Behind the scenes, the compiler will “desugar” the implicit class into a simple class and an implicit conversion method, as we did earlier. Note that implicit classes must have a primary constructor with one argument.

Looking up an appropriate implicit conversion takes time, but it’s not much of an issue because it happens at compile time. The only runtime cost comes from creating an additional instance of RangeMaker for each implicit conversion. The good news is that we can avoid the runtime cost by turning our implicit classes into value classes:

implicit class RangeMaker(val left: Int) extends AnyVal {
  def -->(right: Int): Range = left to right
}

Implicit conversion is a very powerful language feature, but overusing it can reduce the readability and maintenance of the code base.

3.12. Scala class hierarchy

Figure 3.3 illustrates the Scala class hierarchy. The root class in the hierarchy is the class scala.Any. Every class in Scala inherits directly or indirectly from this class. The Scala Any class defines two subclasses: AnyVal and AnyRef. All the values that are represented by an object in the underlying host system (JVM or CLR) are a subclass of AnyRef. Every user-defined Scala class also inherits from a trait called scala.ScalaObject (see section 3.6 for more details). AnyRef is mapped to java.lang.Object in the JVM and to system.Object in .NET.

Figure 3.3. Class hierarchy of Scala with subtypes and views

The subclasses for AnyVal aren’t represented as objects in the underlying host system. This is interesting because all this while I’ve been saying that everything in Scala is an object. It’s still true that everything in Scala is an object, but not at the host system level. When Scala compiles to Java bytecode, it takes advantage of the Java primitive types for efficiency, but converts them to objects when required by the Scala application.

In figure 3.3, along with the subtypes, you have views. Views are implicit type converters that allow Scala to convert from Char to Int to Long and so on. You’ll look into implicit functions later in the book.

Type Scala.Null is the subtype of all reference types, and its only instance is the null reference. The following code creates an instance of scala.Null, and the only way to create an instance of Null is by assigning null to an instance:

scala> val x: Null = null
x: Null = null

You’ll still get an exception when you try to access any method on a Null object. Because it’s subclassed from AnyRef, you can’t assign null to any value type in Scala:

scala> val x: Int = null
<console>:8: error: type mismatch;
 found   : Null(null)
 required: Int
       val x: Int = null

On the other hand, scala.Nothing is at the bottom of the Scala hierarchy, and it’s a subtype of everything in Scala. But you can’t create an instance of Nothing, and there’s no instance of this type in Scala. You’ll learn about Nothing in more detail in chapter 4 because it does solve some interesting problems for Scala.

3.13. Summary

This chapter covered a lot of ground, including how Scala empowers traditional OOP. You learned about new features introduced in Scala 2.8, like named and default arguments. On the one hand, you worked with traits and classes that provide interesting ways to structure your code in Scala; on the other hand, I introduced you to case classes that are handy and useful for building immutable data objects. You’ll work with case classes again when you learn about Actors and concurrency. You also learned about singleton objects and companion objects and how they’re used in Scala.

For the first time you also explored the Scala hierarchy and some of the important base classes. This knowledge will help you easily browse scaladoc when looking for library classes and how they fit into the Scala hierarchy.

This chapter provided a foundation for the things that will follow in consecutive chapters. You’ll revisit object-oriented concepts in Scala in chapter 7, where you’ll explore other abstraction techniques provided by Scala. Remember, a great way to familiarize yourself with Scala concepts is to load up the Scala REPL and try out all the features for yourself. The interactive approach is a good way to learn Scala and understand its concepts. The next chapter should be interesting, because you begin to tackle the various functional data structures in Scala.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset