Interfaces (or are they?)
Scala doesn’t have any direct analogue to Java’s interfaces. This answer may seem a bit surprising, given what I’ve been saying about similarity to Java. The fact is that Java’s interfaces are really a very weak mechanism and a paltry imitation of their forebear: multiple inheritance.Back in the days of C++, when the power of the dark side waxed full, object oriented languages commonly had support for inheriting from more than one superclass. In C++, you could see this in examples like the following (shamelessly stolen from Wikipedia):
class Person { public: virtual Schedule* get_schedule() const = 0; }; class Student : public Person { public: Student(School*); Schedule* get_schedule() const { return class_schedule; } void learn(); private: Schedule *class_schedule; }; class Worker : public Person { public: Worker(Company*); Schedule* get_schedule() const { return work_schedule; } void work(); private: Schedule *work_schedule; }; class CollegeStudent : public Student, public Worker { public: CollegeStudent(School *s, Company *c) : Student(s), Worker(c) {} }; |
The answer is that nobody knows. This is called the diamond problem and it’s something which annoyed computer scientists to no end in the early days of OOP. Of course one solution to the problem is to just never use multiple inheritance. Unfortunately, this becomes extremely restricting in certain situations which really call for the feature.
Java’s designers recognized the need for multiple typing (e.g. CollegeStudent is both a Student and a Worker), but they wanted to avoid the issues associated with inheriting conflicting method definitions along multiple paths. Their solution was to design the interface mechanism, a feature which allows multiple typing without the complications of multiple inheritance. So in Java you would represent the above sample like this:
public abstract class Person { public abstract Schedule getSchedule(); } public interface IStudent { public void learn(); } public interface IWorker { public void work(); } public class Student extends Person implements IStudent { private Schedule classSchedule; public Student(School school) {...} public Schedule getSchedule() { return classSchedule; } public void learn() {...} } public class Worker extends Person implements IWorker { private Schedule workSchedule; public Worker(Company company) {...} public Schedule getSchedule() { return workSchedule; } public void work() {...} } public class CollegeStudent extends Person implements IStudent, IWorker { public CollegeStudent(School school, Company company) {...} public Schedule getSchedule() {...} public void learn() {...} public void work() {...} } |
If the sheer verbosity of the example wasn’t enough to convince you of its flaws, direct your attention to the CollegeStudent class. We’ve achieved our primary goal here of creating a class which is both a Student and a Worker. Unfortunately, we had to implement the learn() and work() methods multiple times. Also, we complicated our hierarchy by a factor of two and introduced the much-despised IRobot convention. Finally, our hierarchy is less constrained than the C++ version in that there’s nothing to prevent us from creating workers which are not people. Logically this makes sense, but our specification says that all students and workers should be people, not insects or computers. So we’ve lost flexibility, been shouldered with verbosity and introduced redundancy, all in the attempt to avoid a trivial logical disjunction.
Traits
Scala recognizes that interfaces have their issues. So rather than blinding creating a reimplementation of the same problems found in either Java or C++, Scala takes a new approach. Inspired by a combination of Java’s interfaces and Ruby’s mixins, the designers of Scala have created the trait construct.trait Book { def title:String def title_=(n:String):Unit def computePrice = title.length * 10 } |
To start with, there’s that ever-annoying override keyword. I mentioned back in the article on basic OOP that any method which overrides a method in a superclass must be declared with the override modifier. At the time, I likened it to the language mandating the use of the @Override annotation, with its primary purpose being to enforce the good practice. The keyword however serves a far more important purpose, as we’ll see in a second.
The real key to the power of traits is the way in which the compiler treats them in an inheriting class. Traits are actually mixins, not true parent classes. Any non-abstract trait members are actually included in the inheriting class, as in physically part of the class. Well, not physically, but you get the picture. It’s as if the compiler performs a cut-and-paste with the non-abstract members and inserts them into the inheriting class. This means that there’s no ambiguity in the inheritance path, meaning no diamond problem. We can rewrite our CollegeStudent example in Scala without redundancy or fear of paradox:
abstract class Person { def schedule:Schedule } trait Student extends Person { private var classSchedule:Schedule = ... override def schedule = classSchedule def learn() = {...} } trait Worker extends Person { private var workSchedule:Schedule = ... override def schedule = workSchedule def work() = {...} } class CollegeStudent(school:School, company:Company) extends Student with Worker { // ... } |
class CollegeStudent(school:School, company:Company) extends Worker with Student { // ... } |
The absolutely most important thing to notice in the example, aside from the multiple-inheritance, is that CollegeStudent is actually a proper sub-type of Person, Student and Worker. Thus you can operate on an instance of CollegeStudent polymorphically as an instance of one of these super-traits. In fact, other than the whole mixin thing, Scala traits are pretty much just like abstract classes. They can declare abstract members, non-abstract members, variables, etc. They can even extend other classes or traits! The one catch is that Scala traits cannot accept parameters in their constructor.
This means that in one sense, the C++ version of our CollegeStudent snippet is more powerful than the Scala translation. In Scala, the Student and Worker traits cannot have constructor parameters, so there’s no way to specify a School or Company as part of the inheritance. If you ask me, it’s a small price to pay for all of this power. And no matter which way you slice it, traits are still far more flexible than Java interfaces.
Type Parameters
You may not have realized this, but Scala is a statically typed, object-oriented language. There are many such languages (C++, Java, etc), but they all have a common problem: accessing instances of a specific type from within a container written against a supertype. An example serves better here than an explanation:ArrayList list = new ArrayList(); list.add("Daniel"); list.add("Chris"); list.add("Joseph"); String str = (String) list.get(0); |
Java 5 saw the introduction of a feature called “generics”. Just about every other language calls them “type parameters”, so I tend to use the terms interchangeably. Java’s generics allow developers to specify that an instance of a generic type is specific not to the supertype but to a specific sub-type. For example, the above example rewritten using generics:
ArrayList<String> list = new ArrayList<String>(); list.add("Daniel"); list.add("Chris"); list.add("Joseph"); String str = list.get(0); |
Because Java 5 was striving to maintain backward compatibility with old code-bases, the generics implementation isn’t as flexible as it could be. Most of this inflexibility stems from the decision to implement generics using type erasure, rather than reified types. However, there are other issues which make working with Java generics weird, such as the incredibly cryptic and inconsistent syntax. In short, Java generics are a language afterthought, whereas Scala type parameters were designed into the language from day one.
Scala type parameters have a lot of nice things going for them. Perhaps number one on the list is a more consistent syntax. Rather than a generics-specific wildcard character ( ? ) and an overloading of the extends keyword, Scala type constraints utilize operators and meta-characters which are consistent with the rest of the language. But I’m getting ahead of myself…
This is how the list example might look translated into Scala:
val list = new ListBuffer[String] list += "Daniel" list += "Chris" list += "Joseph" val str = list(0) |
class MyContainer[T] { private var obj:T = null def value = obj def value_=(v:T) = obj = v } val cont = new MyContainer[String] cont.value = "Daniel" println(cont.value) |
Now Java allows generics to implement type constraining, which forces the type parameter to satisfy certain conditions. (extending a certain supertype is a common example) This can be accomplished easily through Scala, and as I said before, without overloading a keyword:
import scala.collection.mutable.HashMap import java.awt.Component import javax.swing.JLabel class MyMap[V<:Component] extends HashMap[String,V] val map = new MyMap[JLabel] map += "label1" -> new JLabel() map += "label2" -> new JLabel() |
Lower type bounding is when the type parameter is constrained such that a certain type must inherit from the type parameter. This is accomplishable in Java, but only by doing some extremely weird rearranging of the generic definition. In Scala, it’s as simple as switching the direction of the type constraint operator:
class MyMap[V:>CollegeStudent] extends HashMap[String,V] |
Just like Java, Scala allows type parameters on methods as well as classes:
def checkString[A](value:A):A = { value match { case x:String => println("Value is a String") case _ => println("Value is not a String") } value } val test1:String = checkString("Test Value") val test2:Int = checkString(123) |
Again much like Java, Scala allows explicit specification of method type parameters. For example, this is the unsafe idiom to cast objects in Scala (the “sanctioned” syntax is to make use of pattern matching):
val obj:Object = "Test String" val str:String = obj.asInstanceOf[String] |
In truth, we’ve barely scratched the surface of the capabilities of Scala’s type parameter mechanism. If you’re looking for some brainf*ck of an evening, I suggest you read up on Scala type variances. If there was any doubt that Scala is a more powerful language than Java, it will be quickly dispelled.
Oh, as a side note, Scala type parameters are currently (as of version 2.6.1) not quite compatible with Java generics. While you can use parameterized Java classes, specifying the type parameters using the Scala syntax (e.g. new ArrayList[String]), you cannot actually declare parameterized Scala classes to be usable from within Java. According to the mailing-lists however, the next release of Scala will indeed have full cross-compatibility between Scala and Java in the area of type parameterization.
Conclusion
The deeper we go in the Scala language, the more we realize the truly awesome power of its syntax. What’s really amazing though, is that the designers were able to create a language with such flexible constructs without sacrificing the underlying clarity and consistency of the syntax. At first blush, Scala continues to look a lot like Java and remains extremely readable to the average developer. Somehow this clarity is retained without coming at the expense of capability. Traits and Scala’s type parameters are just one more example of this fact.
(codecommit)
0 comments: on "Scala for Java Refugees Part 5: Traits and Types"
Post a Comment