A brief note on syntax first. My language is not going to have a Java like syntax but I’m going being the explanations of the language as if it did. I’m not going to introduce it it’s own syntax, partly because I haven’t nailed down how I want the syntax to look yet, partly to avoid having to explain the syntax while explaining the deeper issues and partly to avoid getting stuck int the quagmire of Wadler’s Law of Langauge Design before I even get to the interesting stuff. So Java seems like the best starting point since it has a static type system and most people are familiar with it.
As of now you know my language will not contain subtyping. It will be somewhat familiar though, in that programs will be made up of classes and interfaces. The first casualty of removing subtyping from the language is that there is no implementation inheritance. A class can still implement zero or more interfaces but it can’t extend another class. Two features which will hopefully make up for this are adding a convenient syntax for delegation and loosening the restrictions on what can be done with interfaces. There are good reasons interfaces can’t contain data fields but there’s actually no good reason they can’t include default implementations of methods. Scala and the Squeak implementation of Smalltalk contain this feature, and call these sorts of interfaces “traits”. Delegation allows one to substitute composition for inheritance more conveniently. I can declare that a member of a class handles a set of methods, rather than providing an implementation for each which calls the corresponding method on that member.
Overall I think removing implementation inheritance isn’t going to be as big a deal as people think. Much of the confusion when learning OO is about when to use composition and when to use inheritance and this removes that decision. It also makes the cases where you want multiple inheritance but can’t use it more consistent and convenient. Additionally it gets rid of the fragile base class problem. So there’s some win here even aside from the type system.
Since there’s no subtyping, interface names aren’t types strictly speaking, they are only constraints on types. If I want to declare a method that works on some sort of list I can no longer make the parameter type List. Interfaces in type declarations now exist exclusively to the right of the “extends” construct in generic parameters, and classes no longer belong there at all. To declare a method which works on all List you would do the following:
<L extends List<X>> void foo(L list);
An enhancement I will use the lack of subtyping to make to the type system is the introduction of a new keyword “ThisClass”. An interface’s methods can be declared to take arguments and return of type “ThisClass”. My langauage won’t have equality built into a base Object type like Java so here’s the interface for things which can be compared for equality:
interface Eq {
boolean equals(ThisClass other);
}
A method to check whether a List contains a certain value would have the following signature:
<E extends Eq, L extends List<E>> boolean contains(L list, E elem);
Note that with subtyping it would be impossible to construct a list that’s guaranteed to be a homogeneous. That would make an Eq interface with a self type method useless for doing this. Also note that without adding any features to this type system it’s impossible to create heterogeneous lists. It’s possible to get that back by being explicit about when it’s ok for a list to be heterogeneous though. Here’s an interface for things which implement toString:
interface Show {
String toString();
}
If I want to turn all the elements in a List into strings, I don’t care if they are all the same type. In this case the signature would look like this:
<L extends List> String concatToStrings(L<? extends Show> list);
Another feature that I can add to this type system is that constructors and static methods can be added to interfaces. The inverse interface to Show is called Read:
interface Read {
static ThisClass fromString(String s);
}
Unlike Java I can use types declared in type parameters to call static methods and constructors polymorphically:
<N extends Number, Read> N readAndAdd3(String s) {
return N.fromString(s) + 3;
}
This turns out to be astoundingly useful, as anyone who has used Haskell can tell you.
So those are the core ideas I’m working with. Hopefully that made a decent amount of sense.