The Goals of Scala's Design

A Conversation with Martin Odersky, Part II

by Bill Venners and Frank Sommers

May 11, 2009




Summary

Martin Odersky talks with Frank Sommers and Bill Venners about the compromises and most important goals in Scala's design, its object-oriented innovations, and what's in it for you.

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.

Next Week

Come back Monday, May 18 for the next installment of this conversation with Martin Odersky. If you'd like to receive a brief weekly email announcing new articles at Artima.com, please subscribe to the Artima Newsletter by clicking its checkbox in your account settings.

Talk Back!

Have an opinion about the history presented in this article? Discuss this article in the Articles Forum topic, The Goals of Scala's Design.

Resources

Martin Odersky is coauthor of Programming in Scala:

http://www.artima.com/shop/programming_in_scala

The Scala programming language website is at:

http://www.scala-lang.org

The Pizza language on Source Forge. Why not try a slice?:

http://pizzacompiler.sourceforge.net/

The nu-object calculus is described in "A Nominal Theory of Objects with Dependent Types", by Martin Odersky, Vincent Cremet, Christine Röckl, and Matthias Zenger. In Proc. ECOOP'03, Springer LNCS, pages 201–225. July 2003:

http://lampwww.epfl.ch/~odersky/papers/ecoop03.pdf (PDF)