What are the advantages of OOP subtyping over typeclasses, if any? In other words, now that we have typeclasses, is there any reason to still use OOP subtyping?
PS: I am a Scala programmer.
What are the advantages of OOP subtyping over typeclasses, if any? In other words, now that we have typeclasses, is there any reason to still use OOP subtyping?
PS: I am a Scala programmer.
One more difference for Scala at least is that chains of subtypes just work, whereas chains of typeclasses are much tricker. If we have types A, B, and C, then if A < B and B < C then necessarily A < C. However, if A <% B and B <% C it is not necessarily the case that A <% C. This is because the Scala compiler will not apply multiple implicit conversions, as otherwise type-inference because difficult and (IIRC) potentially undecideable.
This might help:
http://www.haskell.org/haskellwiki/OOP_vs_type_classes
In a language that is not purely functional, subtyping lets you have different side-effects with the same usage; this isn't always easy to achieve with type classes. (You can achieve it, of course; it just seems more awkward to me.)
Also, subtyping can be more efficient--it's a way to cache the information that "X is a Y" without requiring repeated conversion (or compiler heroics to cache that information) of X to Y. For very deep hierarchies, this could possibly be an issue.
A pragmatic reason to continue support for OOP is interoperability. One of the currently ongoing questions in the BitC discussion is whether to add single inheritance to the language. There are pragmatic pros and cons, and also issues pro and con in its implications for the formal type system and type inference.
For a while, the instance resolution mechanism for type classes had us convinced that type classes were fundamentally flawed for lack of link safety. In the absence of a lexically scoped resolution mechanism, type class instance resolution fails to scale in human terms: a change by one development group can cause linkage errors in an application written in another place by a completely different group. That had us reluctantly looking at single inheritance and some form of F<+SelfType sort of scheme. There are related concerns when instances have multiple resolutions with different degrees of specialization.
We have since come to an approach to instance resolution that looks to resolve this problem to our satisfaction. The question we are wrestling with now is (a) whether BitC programs need subtyping, and if so for what, and (b) even if we don't, whether interoperability with programs in OO languages may nonetheless require us to support a type system in which inheritance is expressible, and consequently a language in which it can be used.
None of which is any sort of conclusive answer to the OP's question. The point, I suppose, is that the issues here go beyond the design of any particular language. There are also human factors and interoperability concerns to be considered.
Jonathan Shapiro
At present, the syntactic overhead of Scala type classes is a good bit larger than for subtyping via trait inheritance, as is the potential runtime overhead. Imagine a case where you need to have fifty different types of events conform to an interface to support an event processing engine. Much easier to write
than
The second form allows a lot more flexibility in terms of post-hoc polymorphism, freedom of naming, and general bad-assery, but typing out those fifty conversion methods and then doing the appropriate imports when the typeclass is needed is going to get to be a right pain. If you don't need the flexibility, it's tough to see the payoff. Plus there's that nagging "new" keyword in the second, which will spawn endless "is this overstressing the garbage-collector" arguments.
The situation is worse for mixin inheritance that introduces mutable state. Consider the following trait, taken from production code:
Incredibly handy use of mixin inheritance, and not really doable with Scala type classes, due to the presence of the "lock" val. Where should it go? If you put it in the adapted class, you lose most of the encapsulation value of the trait. If you put it in the adapter code, the locks no longer protect anything, since you'd be locking on different lock objects every time you're adapted.
Personally, I find OOP easier to deal with within the constraints of what it handles well. In other words: in cases where you don’t actually need typeclasses, I find objects easier to understand.
However, this might just be an artifact of the syntactic overhead that the typical typeclass embedding of objects has. If Haskell had syntactic sugar for some common kinds of typeclass patterns, that difference would probably vanish.
What I find more interesting, is the fact that the Haskell community shows that typeclasses are more powerful than objects, since there exists a trivial embedding of objects in typeclasses, but typeclasses can do things objects can’t. The Scala community, however, shows that objects are at least as powerful as typeclasses1, since there exists a trivial embedding of typeclasses in objects.
This seems to indicate that the relationship between the two is much more intimate than commonly thought.
1 See Type Classes as Objects and Implicits by Bruno C.d.S. Oliveira, Adriaan Moors and Martin Odersky, as well as the discussion of that paper on Lambda the Ultimate, especially this nice summary by Paul Snively (emphasis added):