It’s not keyword tricks, it’s really pretty central to the language. And there is dedicated syntax for it in Scala 3, which was one of the main changes between 2 and 3 – that’s the given
and using
keywords. (I’m just not as practiced in them myself yet, so I used the Scala 2 implicit
keyword.)
@BalmungSan gives a couple of reasons why type classes are a powerful tool. More specifically, the problem of “I want to return the same type that was passed in” turns out to be challenging to get consistently right in traditional OO programming (especially when subclasses get involved), but is relatively easy to handle correctly in type classes.
(See the “F-bounded polymorphism” article that @spamegg1 linked above, which is the usual explanation of this problem – it doesn’t come up super-often, but it can be a doozy of a disaster when it does – I’ve lost days trying to get the F-bounded approach working when doing OO-style code, and you usually don’t realize that you’re in trouble until you’ve already written a ton of code.)
Also, type classes are, essentially, interfaces. Note that my Oneable
above is a type unto itself, which can be passed into higher-level functions; overloads can’t easily do that. That’s super useful, and comes up in real-world code all the time. It means that my higher-level code doesn’t even need to know or care what the concrete type is, just that it is something that is Oneable
.
I should be clear: for your toy example, overloads are fine. But real-world code is usually far more complex – you usually find that you gradually have several different types for which you want to implement a behavior (not just two), often scattered around your codebase, so overloads wind up becoming a centralized and ugly maintanance headache, whereas type class implementations can be distributed around the code as desired.
No, it isn’t the absolute most-concise code one could wish for. But it often turns out to be more correct, more flexible, and more extensible than overload-based solutions, and that’s more important 99% of the time.
It’s by no means the only way to do things (Scala has many tools available), but when the problem is essentially “I have a behavior that I want to implement for multiple unrelated types”, it’s usually the best approach for producing solid, maintainable code.
The result is that I use overloads very rarely – they’re cute, but not very powerful and sometimes more confusing than helpful. (Indeed, I know folks who just plain outlaw them in their codebases, on the grounds that they are more trouble than they are worth.) If I want an overload, what I really mean is that I’m trying to implement the same behavior for multiple types, and type classes usually pay off in the long run as a better way to do that.