First, a bit of background. I love programming languages. I was working on my own now and then before I started getting into Scala. It was closer to Java, but still extensible. But the biggest difference from Scala was that I was targeting it to be very _concrete_.
By this, I mean that Java (while nowhere near as abstract as dynamically typed languages) is still somewhat abstract in nature. Methods are virtual by default (dynamically dispatched). You have to make an extra decision to define things as final. But Josh Bloch says to design for inheritance or forbid it.
While C# doesn't try to avoid inheritance, methods are _not_ virtual by default (something Java doesn't really support for instance methods). This makes it less abstract in its default style than Java. And extension methods are really static, too.
My idea was to make classes and methods default to 'final'. I find from coworkers and myself that it's easier to know what things do if there's only one definition. And I can usually add abstraction just when needed. At least, I think I can.
Scala goes the opposite direction, encouraging generic types, abstract types, everything dynamic dispatch where possible, full object orientation and instance methods, structural typing, mixin traits that allow overriding, and so on. This is doable also, but I'd like to see better support than what Eclipse has for control-clicking on definitions. If it is overridden in the project (or workspace), please show me the options especially if the top level definition isn't fully abstract/undefined.
And it's not too hard to image the abstract meanings of types and methods. And it's not as abstract as dynamic types. But it's an issue that shouldn't be ignored when going into Scala. Scala isn't a language that prefers the concrete.
An interesting observation.
ReplyDeleteI suspect that this is at the foundation of my complaint that Scala has a tendency to be write-only. Especially when no IDE is available to help, trying to locate the code that actually implements an operation can be a pain in a large system.
I also think that the two different interpretations of the "Open/Closed Principle" figure in here.
As originally stated by Bertrand Meyer, the OCP meant that the interface and implementation of a class were frozen once the class was released—refactoring was not permitted. Descendant classes could have not just different implementations but also different interfaces—substitutability was a feature but not a requirement.
This version of the OCP never was widely adopted. Instead, Robert C. Martin's version—the interface is frozen but the implementation can vary, and descendants must implement all of the parent's interface—became the norm.
With this almost universally accepted definition of OCP, refactoring is allowed. We can now change a method in a parent class from being unoverrideable to being overridable without breaking any rules. Making methods overrideable by default was appropriate in Eiffel, but probably is unnecessary in today's OO languages.
It's true that not having an IDE does change how you work with Scala. But it's pretty easy to launch a REPL, instantiate an object, then ask it which class defines one of its methods. This is just a problem of inheritance in OOP and has little to do with Scala.
ReplyDeletefoo.getClass.getMethod("bar",null).getDeclaringClass I think. For implicits, you can ask the compiler to print out a version of the source with all implicits resolved.
I write more functional code with Scala than OO code, and I'm pretty happy thus far.
Thanks to both for the comments. From what I remember from Meyer (DBC and all), I'd be surprised to expect changing interfaces for subclasses. But still, the remarks on OCP are interesting and worth thinking about. And it is right that this subject is closely related to OO. Another title for my post could possibly have been "Scala is more OO than Java is more OO than C#", from an abstract message passing perspective. Of course, dynamic languages like Ruby and Smalltalk take the OO cake vs. static languages. Pros and cons.
ReplyDeleteFrom what I remember from Meyer (DBC and all), I'd be surprised to expect changing interfaces for subclasses.
ReplyDeleteEiffel provides a broad ability to modify the inherited interface, even to the point of deleting inherited features. Here is a discussion from Meyer on the need to be able to modify the inherited interface within subclasses. This quote pretty much summarizes it: "it would be absurd to renounce inheritance --- the reuse of a rich class structure, lovingly developed and carefully validated --- because a feature or two, out of dozens of useful ones, do not apply to our goal of the moment."
Meyer's view was that inheritance was generally about creating new classes which built on existing classes without having to touch the source code of the base classes.
In his view, substitutability is something that might or might not be needed when creating a subclass. If substitutability is not needed, then there is no need for interface consistency between base and derived classes.
That view never gained much popularity. The GoF Design Patterns book pretty much nailed the coffin closed, because 16 of the 23 patterns in that book are based on the expectation of substitutability of subclasses.
The OO world now almost universally considers substitutability to be an absolute requirement of any subclass. I've ranted about that in the past: 1 2 3. Although if people won't listen to Meyer, they sure won't listen to me. :-)
Thanks for the clarification. Been a long time since I skimmed/read OOSC.
ReplyDeleteHi,
ReplyDeleteThank you for nice post. Please keep posting such a nice stuff. I would like to introduce another good C# blog, Have a look.
http://CSharpTalk.com