Yes you can achieve a similar effect by using pattern matching, but like Oliver says sometimes polymorphism is best achieved through pattern matching, but sometimes it's best achieved through OO style.
In particular pattern matching is good when you know the set of things you want to be polymorphic over is limited, but the operations you want to perform on them is not. Pattern matching polymorphism makes it hard to add new types to be polymorphic over, but easy to add new polymorphic operations.
In contrast OO style polymorphism is the other way round. It’s good when you know that the set of operations is limited, but the types you want to perform the operations on is not limited. OO style polymorphism makes it hard to add new operations, but easy to add new types.
The real power comes when you can freely choose whichever type of polymorphism fits your problem best. The problem I describe above fits the OO style much better, the only operations I’m doing is pretty printing … but I can think of all sorts of types that I might want to pretty print.
I think that being able to use extensions methods polymorphically would greatly extend the power of Kotlin. The ability to use ‘any class with an iterator method’ in for loops is very tantalizing and indicates how useful the feature could be. It essentially allows you to say “I can make class X implement interface Y even if I can’t modify class X”. Since all you need to do is define an extension method that returns that interface. You get much of the power of Scala’s implicit conversions, but avoiding almost all of the pitfalls.
I can think of several efficient implementation strategies:
Implementation strategy 1. Dictionaries
The first (as I described above) is to use dictionaries passed as an implicit argument, this is how Haskell does it. As I said above, if Kotlin is going to support the use of class objects in the ways that the compiler currently allows then it’s going to need to do something like that anyway.
Implementation strategy 2. inline
The second implementation strategy is simply to say that you can use structural typing, but the methods in question must be declared inline. Thus my example would need to be:
inline fun <T> List<T>.pretty(): Pretty where T.pretty(): Pretty =
PrettyList(this.map { it.pretty() })
When someone calls the method the compiler inlines the correct .pretty() call for whatever concrete type is used. This is essentially how the ‘for … in’ construct works, the compiler inlines the for-in and inserts the correct .iterator() call at the call site. The constraint that the function must be inline is not a big constraint in practice, and the performance overhead for the feature is essentially zero.
Dictionaries are a bit more powerful: it’s easy to see how to implement
``
class Foo<T> where T.pretty(): Pretty
when using dictionaries … much harder for inline. But inline is likely to be more efficient.