Object-Oriented Scala

This is the fourth in a series of posts that walks through the experience of what it was like to go from being a veteran .NET developer with no awareness of the world beyond the borders of the CLR to exploring the mysteries of the Scala/Akka/Spray stack on the JVM.

The Object-Oriented Side of Things

In my experience, one of the most difficult things to understand is that Scala takes a very narrow view of how it interprets object-oriented principles. First, there are no distinctions between objects and object literals. So the variable myNumber with a value of 1 and the integer literal 1 are equivalent. Second, there is no such thing as an operator. Any operation that an object can invoke is a method. Full stop. So the concept of an addition (+) operator in C# is expressed as a method called + in Scala. So, in C#, I could write an addition expression using two Int32 literals separated by an addition operator like 1 + 2. This yields the Int32 result 3. In Scala, the same concept is expressed like 1.+(2) where 1 is an instance of the Int class that calls the + method supplying the Int argument 2 and returns an instance of Int 3.

This may seem like nothing more than a semantic distinction but it actually has some interesting consequences. In Scala, I can write the expression as 1 + 2 or 1.+(2). The compiler will interpret them the same way. This also applies to any other method I define on an object. For example, suppose I have a class Counter that exposes a method called increment that accepts an integer argument and returns the value of the argument incremented by 1. I could express this as:

class Counter { def increment(n: Int): Int = { n + 1 } }

I could then invoke the method with:

val c = new Counter() val result = c.increment(2)

This seems pretty straightforward. I declare a new instance of Counter and assign it to the variable c. Then I call the increment method supplying the argument 2 and assign the result to the variable result. However, because of the operator notation syntax in Scala, I can also invoke the method as:

val c = new Counter() val result = c increment 2

The expression that uses the operator notation is just as valid and will yield the same result.

The fact that Scala treats everything as classes and methods is much more than a semantic difference. It allows the developer to create extremely descriptive and fluent code.

Everything is Optional (Almost)

The intersection of this strict view of objects and the fact that most of the work of interpreting code is on the compiler means that, from a .NET view, almost everything is optional. In C#, there are certain conventions that are taken for granted and are expected. Things like terminating lines with semicolons, using the dot syntax for methods, certain keywords, and declaring the types of variables explicitly. Scala makes no such demands on the developer.

Semicolons are Optional

As I’ve already shown, the dot syntax is largely unnecessary when writing expressions. The same also applies to semicolons. The only time a statement has to terminated with a semicolon is when there are multiple statements on the same line and, even then, the semicolon is only really necessary if the multiple statements could realistically be interpreted as a single statement by the compiler. In other words, the semicolon is more of a compiler hint than a hard language syntax requirement.

The return Keyword is Optional

In the same vein, Scala regards the return keyword as optional. The idea is that any given method should have a single point of entry and a single point of exit. So it takes the view that the last line of a method body is the point of exit and simply returns it. Looking at the increment example from earlier:

def increment(n: Int): Int = { n + 1 }

You’ll notice that I don’t prefix the expression n + 1 with return. Since it’s the last line of the method body and my method signature indicates a return type of Int, the compiler automatically returns the integer result of the expression. I could have also written the method as:

def increment(n: Int): Int = { val r = n + 1 r }

In this example I’ve assigned the result of the expression n + 1 to the variable r and placed r by itself on the last line of the method body. The compiler takes exactly the same view as before and simply returns r as expected.

Declaring Types is Optional

The last case I’m considering here is declaring variable types. In earlier versions of C# to declare a variable I’d type:

Person p = new Person();

And in more recent versions I’d express the same thing as:

var p = new Person();

The idea is that the type of the variable is inferred based on the type being assigned. Scala gives the same capability with a twist. First, Scala makes a hard distinction between mutable and immutable state. Immutable variables are declared with the val keyword and mutable variables are declared with the var keyword. So, in practice, I could assign a value to a val and, if I tried to reassign it later, the code wouldn’t compile. However, if I declared the variable using var, I could reassign as often as I like. For example:

val a = "A" 
a = "B" // <-- This will break!

var x = "A" 
x = "B" 
x = "C" 
x = "D" // and so on...

The type of the variable (either val or var) is also inferred based on the type being assigned. However, I could explicitly declare the type if I wanted:

val b: Int = 123

This also extends to declaring the return types of methods. Going back to the increment method example, I wrote the method signature as:

def increment(n: Int): Int

The trailing semicolon and Int indicate the return type of the method. I could have written:

def increment(n: Int)

and the compiler would have interpreted it in exactly the same way since the result that is returned by the last line is an Int.

Interestingly, the type declaration on the method argument is required and if omitted, it will break.

At first, this all seems odd when compared to .NET and something I noticed that’s made it easier to think about is that the name:type style of notation is also used in the flavor of UML that I’m used to working with. So, after a while, it feels less strange to write code in this fashion and more like a highly expressive flavor of UML and, somehow, that makes it easier.

Handling Interfaces and Inheritance

The other key thing to making the translation from a .NET to Scala way of thinking is how the concept of C#’s interfaces and inheritance are handled. The idea of an interface is that it represents a contract specifying what an object can do without providing the details of how it does it. The Scala view of interfaces is significantly different and more closely related to the header files in C/C++ than .NET. For example, in .NET I could create a series of code constructs to represent a person similar to figure 2:

Figure 2: A .NET Retireable Person

Figure 2: A .NET Retireable Person

In the diagram the interface IPerson describes the FirstName and LastName properties, the IRetireable interface describes an Age property and a YearsTillRetirement method that accepts a retirementAge integer argument and returns an integer result. The Person class implements the IPerson interface. Then, finally, the RetireablePerson class inherits Person and implements the IRetireable interface by calculated the years remaining till retirement. This could be represented in code as:

public interface IPerson 
{ 
    string FirstName { get; set; } 
    string LastName { get; set; } 
}

public interface IRetireable 
{ 
    int Age { get; set; } 
    int YearsTillRetirement(int retirementAge); 
}

public class Person: IPerson 
{ 
    public Person() {} 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
}

public class RetireablePerson: Person, IPerson, IRetireable 
{ 
    public RetireablePerson() : base() {} 
    public int Age { get; set; } 
    public int YearsTillRetirement(int retirementAge) 
    {
        return retirementAge - this.Age; 
    } 
}

Scala represents inheritance by extending a class and interfaces through the use of traits. Traits have more in common with C/C++ header files and aspect-oriented programming than the .NET view of interfaces. A trait represents a set of functionality that is modular and pluggable and is attached to classes. Once attached, the class has access to both the definition and implementation of the variables and methods described on the trait. So, the diagram representing the same RetireablePerson in Scala would be similar to figure 3:

Figure 3: A Scala RetireablePerson

Figure 3: A Scala RetireablePerson

In this instance, the IPerson interface has been dropped, the IRetireable interface is represented by the Retireable trait, and the RetireablePerson class extends Person and includes Retireable. Represented in code, this would be similar to the following:

trait Retireable { 
    var age: Int = 0 
    def yearsTillRetirement(retirementAge: Int): Int = { 
        retirementAge - age 
    } 
}

class Person { 
    var firstName: String = "" 
    var lastName: String = "" 
}

class RetireablePerson extends Person with Retireable {}

The Scala version is much more compact than the .NET version and the RetireablePerson class essentially represents the intersection of the Person class and Retireable trait.