Scala is an emerging general-purpose, type-safe language for the Java
Platform that combines object-oriented and functional programming.
It is the brainchild of Martin Odersky, a professor at Ecole
Polytechnique Fédérale de Lausanne (EPFL). In this multi-part interview
series,
Artima's Frank Sommers and Bill Venners discuss Scala with Martin
Odersky. In Part I, The
Origins of Scala, Odersky gives a bit of the history that led to
the creation of Scala. In this installment, Odersky discusses the
compromises, goals, innovations, and benefits of Scala's design.
Design compromises in Scala
Frank Sommers:
You mentioned earlier that you wanted to create a language that would
exist in the Java ecosystem and
integrate with the Java infrastructure. In order to do that what sort of
compromises to Scala to make
it compatible with the Java Platform?
Martin Odersky: There were fortunately not very many
compromises we had to make. Or
let's say, it's debatable whether the compromises we made were
altogether a bad thing, or whether they
were actually good. One compromise we had to make was to buy into the
model of static overloading
in Java. We might have wanted to experiment with something more radical
such as multi-methods, although
at the time we did that, the multi-methods designs were not sufficiently
explored. Maybe they are not
sufficiently explored even today, such that I'm not completely sure this
would have been a good thing.
It would have been an exciting possibility to explore, but we didn't do
it because we wanted to stay
compatible with Java.
Another thing, which people sometimes criticize, is that Scala has both
traits and classes. A cleaner design
could make do with just traits. There have been some clean designs that
only have traits and give
up the notion of classes. But we didn't do that because we wanted to
stay interoperable with Java
in both directions. We wanted to have a way where Java code could easily
call Scala code, and
traits don't have a natural mapping back into Java, because they don't
exist in Java. Thus we picked
the class design as it was in Java because we wanted the backwards
mapping, so we could easily
have interoperability in both directions.
A third one, which is more a library problem than a language problem,
really, is that we want to
move away from null. Null is a source of many, many errors. We could
have come out with a language
that just disallows null as a possible value of any type, because Scala
has a perfectly good
replacement called an option type. But of course a lot of Java libraries
return nulls and we have
to treat it in some way.
Picking battles in Scala's design
Bill Venners:
What did you put in Scala for the purpose of making Scala more
acceptable to Java programmers? For example,
something like Java's use of curly braces, which might make C and C++
programmers feel more at home than if
Java had used some other block demarcation scheme.
Martin Odersky: We didn't really design things in for
marketing reasons, but we did want to avoid
putting up hurdles—things programmers would find wierd—just because we
wanted to be different. For example, I think
curly braces work perfectly well as delimiters, and that's
why I picked them. We could have insisted on begin/end or something
else, but I don't think it actually works
better.
There was one thing that we changed from a more pure initial design.
Initially we had colon-equals
for assignment—just as in Pascal, Modula, and Ada—and a single equals
sign for equality.
A lot of programming theorists would argue that that's the right way to
do it. Assignment is not equality, and
you should therefore use a different symbol for assignment. But then I
tried it out with some people coming
from Java. The reaction I got was, "Well, this looks like an interesting
language. But why do you write colon-equals?
What is it?" And I explained that its like that in Pascal. They said,
"Now I understand, but I don't understand why
you insist on doing that." Then I realized this is not something we
wanted to insist on. We didn't want to say, "We
have a better language because we write colon-equals instead of equals
for assignment." It's a totally minor
point, and people can get used to either approach. So we decided to not
fight convention in these minor
things, when there were other places where we did want to make a
difference.
Bill Venners: You didn't use the phrase, "pick your
battles," which I've heard you say before. Basically,
you felt that the colon-equals was not that important, and there were
other things that you did care about battling for.
What are those important things? What are the ways in which you really
want to convince people to change how they
think or program?
Martin Odersky:
The first thing we cared about was to have as clean an integration of
functional and object-oriented programming as
possible. We wanted to have first-class functions in there, function
literals, closures. We also wanted to
have the other attributes of functional programming, such as types,
generics, pattern matching. And
we wanted to integrate the functional and object-oriented parts in a
cleaner way than what we were
able to achieve before with the Pizza language. That was something we
deeply cared about from the start.
Later on, we discovered that this was actually very easy, because
functional languages have a fixed
set of features. They had been well researched and well proven, so the
question
was only how to best integrate that in object-oriented programming. In
Pizza we did a clunkier attempt, and in
Scala I think we achieved a much smoother integration between the two.
But then we found out that
on the object-oriented side there remained lots of things to be
developed. Object-oriented programming, at least
when you throw in a static type system, was very much terra incognita.
There was some work we could look at and use, but
almost all languages that we found had made a lot of compromises.
So as we developed Scala, we started to discover how
we could mix objects together with traits and mixin composition, how we
could abstract the self types, how
we could use abstract type members, and how we could let this all play
together. Up to then there had been a
couple of research languages that addressed a few of these aspects in
specialized ways, but
there wasn't much in terms of mainstream languages that covered the
whole spectrum of
making it all work. In the end it turned out that the main innovations
in Scala were on the object-oriented
side, and that's also something we really cared about.
Object-oriented innovations in Scala
Bill Venners:
Just to clarify, could you give a specific list of what you consider to
be the object-oriented innovations in Scala?
Martin Odersky:
First, we wanted to be 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. So we didn't want statics,
but we needed something to replace them, so
we created the construct of singleton objects. But even singleton
objects are still global structures. So the
challenge was to use them as little as possible, because when you have a
global structure you can't change it anymore.
You can't instantiate it. It's very hard to test. It's very hard to
modify it in any way.
The challenge was, therefore, how to enable complicated components to be
built and composed without referring to statics or globals. In
particular,
we had to deal with
recursive dependencies between components. I have two components, A and
B. A uses B, and B uses A. How do I have them discover
each other and let them work together? The first thing we did was this
notion of mixin composition. Whereas Java just
has a single class and a bunch of interfaces that have abstract
definitions but no code, Scala has classes and traits,
where a trait can contain methods with definitions—i.e., with
bodies—as well as fields. And then instead of just having
the class implement interfaces, we have mixin composition, where we take
the definitions of a class and all
the traits. The details of how this is done and exactly what definition
actually ends up in the compiled class is tricky and is
called linearization.
So we had to define a linearization ordering for Scala, which we did.
But then the problem
became how can a mixin discover all the other mixins? Say they need some
service from another mixin;
how can they specify that? The standard object-oriented way to do that
is through abstract
members, and that works well as long as you're dealing with methods. But
we also had to deal with
variables. How can mixins find each others' fields? And more
importantly, we had to deal with types.
Because we had type nesting from the start, something like inner
classes, which is another thing I believe
is really important. If you do mixin composition, how can a trait find
out about the inner classes
of all the other traits, so they can get at those classes? We
discovered—it was
more of a discovery than a design—that we could do that by having an
abstraction over the self type.
So what does "abstraction over the self type" mean? Typically in a
class, what's the type of the this
reference? You would
say, "Well, it's the type of the class." That's what most people say.
There's actually no compelling reason
why it has to always be that. The type of this
could very
well be something else. There's only
one boundary condition, which is that by the time you actually create an
instance of a class, then the
object you create had better have the same as the idea of what this
is that the class has of itself.
It's exactly what happens when you deal with
abstract methods. You can very well have an abstract method in a class,
and you don't need an implementation.
It's actually not dangerous, because by the time you create an instance
of that class, you will have checked
that all abstract methods have an implementation. So, abstracting over
the self type is just a generalization
of that, which also lets you deal with fields and, more importantly,
with types.
This technique of abstracting over the self-type was very rewarding to
discover.
It happened when we built a calculus early on to study these things
formally. It was called the nu-object
calculus—νObj, with the Greek letter ν—and we
published it in the European Conference on Object-Oriented Programming
in 2003 (ECOOP 2003). We originally discovered this notion
as just a technical trick to make some of the treatment simpler in the
calculus. Only later on did it occur to us that maybe this would be
useful
for something in the language. We didn't really know too much yet about
what it would be useful for, but we decided
to try and put it in. Since it was our calculus, we decided to do that.
Only later on did we discover that it
achieves the fundamental principle of letting a trait declare what it
requires of the other traits with which
it will be mixed in.
That's actually something that's solved nowadays by tools such as
Spring, and it's called dependency injection. Except that dependency
injection typically only works for
fields, and maybe for methods, but we have it working for types as well.
Also, in Scala it's done
statically rather than at runtime, and secondly, it gives us type safety
for these inner types. So in
a sense we can push it much further than what the tools such as Spring
can currently do.
What's in it for me?
Bill Venners:
We're going to get back to dependency injection later in the interview,
but other than dependency injection,
the only other question we have for this installment of the interview
is, why? You just talked "abstractly" about
self types. The two things you mentioned as battles you did want to pick
were the blending of functional
and object-orientation, and the object-oriented innovations. If I'm
programming in Java for my job, how can
these things help me in the real work I'm doing? What kind of concrete
benefits would I get?
Martin Odersky:
One of the challenges we were facing is we wanted to be both functional
and object-oriented. We had very
early on the notion that immutable objects would become very, very
important. Nowadays everybody talks
about immutable objects, because people think they are a key part of the
solution to the concurrency problems caused by multi-core computers.
Everybody says, no matter what you do, you need to try to have as much
of your code using immutable
objects as possible. In Scala, we did that very early on. Five or six
years ago, we started to think very
hard about immutable objects. It actually turns out that a lot of the
object-oriented field up to then
identified objects with mutability. For them, mutable state and objects
were one and the same: mutable
state was an essential ingredient of objects. We had to, in essence,
ween objects off of that notion, and
there were some things we had to do to make that happen.
For instance, with a standard Java object, you
would create a field, essentially a mutable field. You would then have a
constructor that takes
parameters and assigns a value into the field. That's a notion of
mutability that's built right there
into every Java class. Now, Java has final fields, which are not treated
as mutable because they are
assigned only once in the constructor. But you still see the assignment
operator.
We wanted to have a much cleaner notion where you don't have this
constructor and
assignment thing.
What we ended up doing in Scala is parameterizing classes directly. You
just write a parameter list after your class name, and those are the
parameters of the class.
There's no notion of having a separate mutable field and a constructor
that assigns into it, and
that actually turned out to cause a number of issues that we had to
solve. One was, what happens
if you want to have several constructors? We had to define a syntax and
rules for that. You can
have
auxiliary constructors in addition to your
primary
constructor. Another
issue was, what if you want to have your parameter visible as a field
later on? Do you need to create
a separate field and assign to it? Or is it possible to pass a parameter
into a class that immediately
becomes available as a field for everyone to see? So we had to create
syntax for that, which I believe
is also new. And once you do that you have to worry about rules for
overriding these things, so
we had to address that. In the end it was quite a lot of novel ground on
the object-oriented side to
make it all work.
Bill Venners:
And the benefit for me, a Java programmer, is?
Martin Odersky:
The benefit for you is you can write classes much more concisely in a
syntax that doesn't look
like you were using state. It is generally easier and more concise to
write your classes in Scala, and it
becomes far easier if what you want to do is immutable objects. Because
it's really geared for
that. You can do mutable objects in Scala, as in Java. They would even
be still a little more
concise in Scala than in Java, but Scala really shines for immutable
objects. It's much more
natural and concise than what you do in Java.