We’re certainly not there yet, and we—everyone—have plenty more to explore first. Always reflect on what you’d like in a language rather than sticking to what existing languages give you and soldiering on. Thinking about type systems is part of this.
Here’s a relevant and poignant example from Java. If you’ve used Java, you might have needed the .clone() method a few times. Recall how it gets used, though—typically like this:
| Foo orig = Foo.new(); |
| Foo copy = (Foo) orig.clone(); |
Why is that cast needed on the second line? Is it just because that’s the Java way and you’ve gotten used to it? Or does it point to a weakness in the language? It is rather annoying to have to write down information that is very obvious to the programmer, which also adds to the “noise” in the program and obscures the “signal.”
Sadly, it is a language weakness: Java’s type system imposes certain constraints in order to get its version of inheritance to work “safely,” and this means that inherited methods get restricted types. An overridden method must return the same type as the method in its ancestors.
Like most interesting aspects of life, designing a type system is a bit of a balancing act or trade-off, of juggling flexibility and articulacy with being able to retain useful properties and still be usable by programmers.
Some languages have a good balance, others not, and I’d place Java in the latter camp: the benefits are quite modest, and the costs too high. I would like to put Haskell in the first camp.
However, Java’s issues don’t mean that type checking in OOP is inherently problematic, or that the only alternative is dynamic checking—even if some people try to sell you this argument. There is much good work in the area that develops alternative approaches. I recommend Kim B. Bruce’s Foundations of Object-Oriented Languages (2002) as a useful starting point. It has a highly readable first section that surveys the main languages, including some illuminating discussion of Java’s design and implementation. Then it proposes several attractive alternatives to Java. A key aspect of Bruce’s approach is keeping data and functionality separate, making serious use of interfaces for the latter.