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:
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:
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.