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.
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
class MyEvent extends Event{ val name = "foo" }
than
class MyEvent{ val name = "foo" } object MyEvent2Event{ implicit def convert(myEvent:MyEvent) = new Event{ val name = myEvent.name} }
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:
trait Locking{ private val lock = new ReentrantReadWriteLock() def withReadLock[T](body: => T):T={ try{ lock.readLock.lock() body }finally{ lock.readLock.unlock() } } // same for withWriteLock }
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):
Martin Odersky and team’s design decisions around how to do type classes in a unified OO and FP language continue to bear fascinating fruit. Implicits look less and less like “poor man’s type classes,” and more and more like an improvement upon type classes, in my opinion given a quick read of this paper.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With