However, before going into the details of all mechanisms, let us look at how Scala differentiates itself in expressing the basic construct of object oriented programming - the class definition. The following example is adapted from Ted Neward (with some changes) :
class Person(ln : String, fn : String, s : Person) {
def lastName = ln;
def firstName = fn;
def spouse = s;
def this(ln : String, fn : String) = { this(ln, fn, null); }
private def isMarried() = {spouse != null;}
def computeTax(income: double) : double = {
if (isMarried()) income * 0.20
else income * 0.30
}
override def toString() =
"Hi, my name is " + firstName + " " + lastName +
(if (spouse != null) " and this is my spouse, " + spouse.firstName + " " + spouse.lastName + "." else ".");
}
Let us quickly browse through the salient features of the above type definition where Scala differs from traditional OO languages like Java.
- The main highlight is the conciseness or brevity as Ted Neward points out in his blog, where he also does an interesting comparison of the brevity of class definitions in Java, C#, VB, Ruby and Scala. Try writing the same definition in any of the mentioned languages - you will get the point straight through!
- The class declaration itself takes the arguments for the primary constructor. The member definitions in the next three lines take care of the rest of the initialization. As a programmer, I have to type less - this is cool stuff! The constructor for an unmarried
Person
is defined as a separate constructordef this(...) ..
. - Like Ruby, Scala provides accessor methods directly around the fields. Being a functional language also, Scala does not provide default mutator semantics in the class definition. In order to mutate a field, you have to do it explicitly through state mutator methods.
- Any class in Scala with an unspecified parent inherits from
AnyRef
, which is mapped tojava.lang.Object
in the Java implementation. The overriding of a method from the parent is explicitly decorated with the keywordoverride
- the rest of the inheritance semantics is same as Java.
Class Composition in Scala
The rich type system of Scala offers powerful methods of class composition, which are significant improvements over that provided by popular OO languages like Java. Modular mixin composition of Scala offers a fine-grained mechanism for composing classes for designing reusable components, without the problems that techniques like multiple inheritance bring on board.
For effective class composition, Java offers "interfaces" in addition to single inheritance, while C++ offers direct "multiple inheritance" through classes. The problem with Java interfaces is that they contain only the method signatures, while the implementer of the interface has to provide implementation for all the methods that belong to the interface. Hence factoring out common implementations as reusable artifacts is not possible. OTOH, multiple inheritance in C++ offers direct and symmetric composition of classes without introducing any additional construct. But multiple inheritance brings with it the dreaded diamond syndrome - name conflicts and ambiguities. While this can be controlled by using virtual inheritance, still factoring out reusable functionalities in the form of composable abstractions is clumsy, at best, in C++. The idiom of using generic superclasses popularized by Van Hilst and Notkin, to implement mixins, provides a solution, but still this does not make mixins a part of the C++ typesystem. For more details, check this excellent treatment on the subject.
Scala's mixin-class composition allows programmers to provide default implementations at the mixin level itself, thereby reusing the delta of a class definition, i.e., all new definitions that are not inherited. Mixins in Scala are similar to Java interfaces in that they can be used to define contracts / signatures for subtypes to implement. The difference is that the programmer is allowed to provide default implementations for some of the methods which are parts of the mixin. While this may appear similar to what abstract classes provide in Java, but mixins can be used as a vehicle to implement multiple inheritance as well (which Java abstract classes do not offer). And finally, mixins, unlike classes may not have constructor parameters.
To define a class that will be used as a mixin for composition, Scala uses the keyword
trait
.// defining a trait in Scala
trait Emptiness {
def isEmpty: Boolean = size == 0;
def size: int;
}
In order to compose using traits, Scala offers the keyword
with
:// compose using traits
class IntSet extends AbstractSet with Emptiness with Guard {
....
}
The following are the main characteristics of trait types in Scala :
- Trait classes do not have any independent existence - they always exist tagged along with some other classes. However they provide a seamless way to add behaviours to existing classes while allowing default implementations to be defined at the base level.
- Nierstrasz et. al. observes
An interesting feature of Scala is that traits cannot only be composed but can also be inherited, which is a consequence of the fact that Scala traits are just special classes. This means that both classes and traits can be defined as an extension of other traits. For example, Scala allows one to define a trait B that inherits from a trait A and uses the two traits U and V :
- In Scala, traits are first class citizens of the type system, in the sense that they are as much part of the type system as are classes. Traits, like classes introduce a new type and can be seamlessly integrated with the generics implementation of Scala. But that is for another day's story.
trait B extends A with U with V {
...
}
In order to get a feel of the power of mixin-composition in Scala using traits in the real world, here's an example from Odersky :
// define an abstract class for the iterator construct
abstract class AbsIterator {
type T;
def hasNext: boolean;
def next: T;
}
// enrich iterators with an additional construct foreach
trait RichIterator extends AbsIterator {
def foreach(f: T => unit): unit =
while (hasNext) f(next);
}
// yet another iterator for Strings
// provides concrete implementations for all abstract
// methods inherited
class StringIterator(s: String) extends AbsIterator {
type T = char;
private var i = 0;
def hasNext = i < s.length();
def next = { val x = s.charAt(i); i = i + 1; x }
}
// usage
// composition of StringIterator with RichIterator
// Using class inheritance along with trait inheritance
// without considerations for multiple definitions
// : only delta is inherited
object Test {
def main(args: Array[String]): unit = {
class Iter extends StringIterator(args(0))
with RichIterator;
val iter = new Iter;
iter foreach System.out.println
}
}
Mixins in Ruby - Compared
Going thru the above piece, astute readers must have been wondering about how the mixin class composition in Scala stands with respect to some of the other implementations in modern programming languages. Let me end this post with a small comparison of the similar feature as offered by Ruby.
Mixin implementation in Ruby is offered through modules. Define a
module
in Ruby, include
it within your class - and you have access to all instance methods of the module. It's as simple as that - when it's Ruby, simplicity is the essence. // define a module
module Debug
def who_am_i?
"#{self.class.name} (\##{self.object_id}): #{self.to_s}"
end
end
// include in class
class Phonograph
include Debug
# ...
end
class EightTrack
include Debug
# ...
end
// get access to instance methods
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.who_am_i? // "Phonograph (#937328): West End Blues"
et.who_am_i? // "EightTrack (#937308): Surrealistic Pillow"
But, modules in Ruby have its own clumsiness when it comes to defining instance variables within the mixin and resolving ambiguities in names. Moreover, modules are not strictly part of Ruby's type system - Scala makes it more elegant by seamlessly integrating the
trait
with its class based type system. The moment you achieve this, generics based mixins come for free. And that will be one my topics for the next installment - another powerful abstraction that makes component design easier in Scala - The Abstract Type Member.
1 comment:
Regarding the brevity aspect of Scala, I discovered the following post in the Scala mailing list, which suggests a further improvement. Adding a val modifier to the constructir parameters make them automatically available within the class. This is really cool stuff!
Post a Comment