I don't see how it can be argued that 'L' is intuitive to intermediate level developers. Even for senior developers you still have to stop and think rather than trust your gut. Developers will very intuitively see the bass class as the one that handles the most common cases, and subclasses as the exceptions that "override" and vary things a little bit.
My favorite example is the Doctor that can see all patients, and the Pediatrician that sees only children. Intuition would make Pediatrician the subclass of Doctor, when it should be the other way around. [1]
> Developers will very intuitively see the bass class as the one that handles the most common cases, and subclasses as the exceptions that "override" and vary things a little bit.
This intuition is wrong. Java-style classes don't handle the cases you think they handle. In particular, you don't have any guarantees at all about the meaning of non-final methods, precisely because they can be overridden by subclasses anytime! They're as good as abstract, and the implementation provided is “for reference purposes only”.
If you need stronger guarantees than that, then you must redefine the relationship between subclasses and subtypes as follows:
(0) A subclass that mutates inherited fields isn't a subtype of its superclass.
(1) A subclass that overrides inherited non-abstract methods isn't a subtype of its superclass.
But the result would be a different programming language, not the Java/C#/whatever you have today.
> My favorite example is the Doctor that can see all patients, and the Pediatrician that sees only children.
Then either (0) it isn't true that a Doctor can see all patients, or (1) Pediatrician isn't a subtype of Doctor. If you want your types to model real life, you have to go for the former.
Let's make sure we agree on a definition of "intuitive". I am referring to the probability that an average developer will grasp the subtyping concept and understand it instinctively once explained to the point they will easily do it correctly from that point forward. I am not referring to the inherent beauty of the concept.
By that definition, my argument is that proper subtyping is counterintuitive to an average developer. Your statement that it's intuitively obvious strikes me as an opinion about the concept itself, rather than the ability of average developers to grasp it.
As for the first quote of mine, yes, I agree, that intuition I described is wrong, and that is absolutely my point. It is wrong, and yet it is very common for developers to have that wrong intuition. So it is therefore not intuitive. That's why I brought it up - that (and trying to make a pediatrician a subtype of doctor) are very common areas of confusion for people trying to understand proper subtyping.
> Let's make sure we agree on a definition of "intuitive". I am referring to the probability that an average developer will grasp the subtyping concept and understand it instinctively once explained to the point they will easily do it correctly from that point forward.
Yes, I mean that too.
> I am not referring to the inherent beauty of the concept.
Me neither.
> By that definition, my argument is that proper subtyping is counterintuitive to an average developer.
I disagree. People aren't stupid, and are able to grasp basic distinctions once they're pointed out, especially when they're newcomers to a topic. On the other hand, once they have some experience, it's harder for them to get rid of their misconceptions.
> Your statement that it's intuitively obvious strikes me as an opinion about the concept itself, rather than the ability of average developers to grasp it.
Contravariance of argument types and covariance of return types can be explained in 5 minutes to anyone with basic logical reasoning skills. People who lack such skills have no business being programmers.
> It is wrong, and yet it is very common for developers to have that wrong intuition.
That's because they've been lied to since they were beginners. It's not true that:
(0) Subclassing and subtyping are the same thing.
(1) You can understand the semantics of programming language features (such as subclasses and subtypes) in terms of natural language analogies.
> (and trying to make a pediatrician a subtype of doctor)
Trying to make Pediatrician a subtype of Doctor is correct. What is wrong is pretending that every Doctor can treat any Patient. Unfortunately, implicit subtyping in the presence of abstract types (not the same thing as abstract classes!) is rather tricky, but, assuming you don't mind upcasting manually (which is perfectly safe, albeit tedious), this is readily expressible in Standard ML:
signature MEDICAL_ATTENTION =
sig
type doctor
type patient
(* no result is passed to the continuation,
* we only run this for its side effects *)
val treat : doctor * patient -> unit
end
structure Pediatrics :> MEDICAL_ATTENTION
where type patient = child =
struct
type doctor = ...
type patient = child (* assumed defined elsewhere *)
fun treat (d, p) = ...
end
structure Cardiology :> MEDICAL_ATTENTION
where type patient = person =
struct
type doctor = ...
type patient = person (* assumed defined elsewhere *)
fun treat (d, p) = ...
end
val child_as_person : child -> person
As can be seen, some doctors (e.g., `Cardiology.doctor`s) can treat any person, but not all.
My favorite example is the Doctor that can see all patients, and the Pediatrician that sees only children. Intuition would make Pediatrician the subclass of Doctor, when it should be the other way around. [1]
[1] http://closuretools.blogspot.com/2012/06/subtyping-functions...