Like you, I wish that discriminated unions were more prevalent; however, the reason they are useful in most functional languages is that they provide exhaustive pattern matching, and without this, they are just pretty syntax: not just pattern matching: exhaustive pattern matching, so that the code doesn't compile if you don't cover every possibility: this is what gives you power.
The only way to do anything useful with a sum type is to decompose it, and branch depending on which type it is (e.g. by pattern matching). The great thing about interfaces is that you don't care what type something is, because you know you can treat it like an iface
: no unique logic needed for each type: no branching.
This isn't a "functional code has more branching, OO code has less", this is a "'functional languages' are better suited to domains where you have unions - which mandate branching - and 'OO languages' are better suited to code where you can expose common behaviour as a common interface - which might feel like it does less branching". The branching is a function of your design and the domain. Quite simply, if your "heterogeneous but logically associated types" can't expose a common interface, then you have to branch/pattern-match over them. This is a domain/design problem.
What Misko may be referring to is the general idea that if you can expose your types as a common interface, then using OO features (interfaces/polymorphism) will make your life better by putting type-specific behaviour in the type rather than in the consuming code.
It is important to recognise that interfaces and unions are kind of the opposite of each other: an interface defines some stuff the type has to implement, and the union defines some stuff the consumer has to consider. If you add a method to an interface, you have changed that contract, and now every type that previously implemented it needs to be updated. If you add a new type to a union, you have changed that contract, and now every exhaustive pattern matching over the union has to be updated. They fill different roles, and while it may sometimes be possible to implement a system 'either way', which you go with is a design decision: neither is inherently better.
One benefit of going with interfaces/polymorphism is that the consuming code is more extensible: you can pass in a type that wasn't defined at design time, so long as it exposes the agreed interface. On the flip side, with a static union, you can exploit behaviours that weren't considered at design time by writing new exhaustive pattern-matchings, so long as they stick to the contract of the union.
Regarding the 'Null Object Pattern': this isn't not a silver bullet, and does not replace null
checks. All it does it provide a way to avoid some 'null' checks where the 'null' behaviour can be exposed behind a common interface. If you can't expose the 'null' behaviour behind the type's interface, then you will be thinking "I really wish I could exhaustively pattern match this" and will end up performing a 'branching' check.
In your example, you don't really show the same message, you show two different messages that happen to have the same name. Polymorphism requires that the sender of a message can send it without knowing the exact recipient. Without seeing evidence that the caller can do something like shape.draw()
without knowing whether shape
contains a circle or a rectangle, you may or may not have actual polymorphism. They could be as unrelated as circle.draw()
and weapon.draw()
.
They don't necessarily have to both implement the same nominal interface. The language could support structural typing or compile-time templating and it would still be called polymorphism. As long as the caller doesn't care who the callee is.
Best Answer
As you have found, there are many different definitions of polymorphism. This answer will address it from a practical perspective and may not align with academic definitions.
Sub-type polymorphism (via. inheritance)
This is the kind of polymorphism I believe most people think of with regard to the term polymorphism. This is where methods are inherited by sub-classes and then can be overridden. For example (Python 2.7):
Output:
This works just sub-type polymorphism would in Java or C#.
Duck Typing
Python also has a feature called duck-typing. Technically this is also sub-type inheritance if you distinguish sub-types and inheritance (which I think is the right way to think about Python) The term comes from the idiomatic saying: "if it looks like a duck and quacks like a duck, it's probably a duck" which is often shortened to "if it quacks like a duck..." I would argue that whether or not this is polymorphism in the academic literature doesn't really matter because it can be used to solve the same problems:
Output:
As you see we can get the same kind of behavior as before but now Bar is not a Foo. Duck-typing is very commonly used in Python programs where sub-typing is not. For example, it's common to create list-like objects where all (really: most) of the operations of a
list
are implemented and it is passed into methods as if it is aslist
even though from a pure typing perspective, it is not actually a type derived from list.Parametric Polymorphism
I take this to mean stuff like generics in Java or C#. I don't think this is relevant to Python but I'm not up to speed on Python 3.
Method overloading
This an interesting case. From a pure Python perspective, this isn't meaningful because you can't overload methods. That is, you can't define the same method name twice at all. What's interesting is that in Jython (python that compiles to run on the JVM) you get a feature that neither Java nor Python have: double-dispatch. When determining what version of an overloaded Java method to call from a python statement, the runtime will look at the actual types of the parameters of the call and select the version dynamically. In Java overloaded methods are bound to calls at compile time instead which is technically polymorphic but in a really uninteresting way. Double-dispatch overloading can actually be used to implement some interesting effects. I expect that this would work similarly in IronPython and calls out to C# libraries.
I think I've covered most of the bases here. Comments are welcome for additional features that might be considered polymorphic. One thing that I'm not sure about is the ability to create synthetic or virtual properties/methods using
__getattr__
or__setattr__
et al. Also, I think you could make the argument that passing method references around is type of polymorphism but maybe that's stretching things a bit too far.