19

I often read definitions for Polymorphism such as the following:

Polymorphism is the ability to have objects of different types understanding the same message

But the above definition also apply if we don't use Polymorphism, for example if we have an object of type Circle with a method draw(), and another object of type Rectangle with a method draw(), we can do:

circle1.draw();
rectangle1.draw();

So circle1 and rectangle1 understood the same message draw() without using Polymorphism!

Am I missing something?

  • 7
    "Am I missing something?" What is the Supertype of Circle and Rectangle? – πάντα ῥεῖ May 6 at 11:50
  • 12
    @πάνταῥεῖ don't forget that they can have different supertypes yet both implement the CanDraw interface. Or this might be a language that supports duck typing. Polymorphism comes in many forms. – candied_orange May 6 at 12:17
  • 1
    . In golang for example your circle and rectangle classes might be implicitly implementing multiple interfaces. If you reference the interface to call Draw() then you are exploiting polymorphism. Just because you don't does not mean your code is not polymorphic. The downvoted answer on modus tollens is basically correct. What you are up against is your own logical fallacy. Everything else you understand about polymorphism, including your quote, still holds. – Frank May 6 at 21:29
  • 2
    It should be noted that your example is also a kind of polymorphism. – max630 May 7 at 5:08
  • 14
    Polymorphism is confusing. It seems like it's one thing, but then it seems like it's something else. – Paul D. Waite May 7 at 9:47

10 Answers 10

69

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.

| improve this answer | |
  • In fact, this whole example is exactly how rendering engines work - the most basic of algorithms is literally to "sort objects by distance from camera, then call draw" - obviously some efficiencies have been made along the way... – corsiKa May 7 at 3:27
  • 2
    @Frank That’s explicitly addressed in the answer (Go does structural subtyping). – Konrad Rudolph May 7 at 8:16
  • "Polymorphism requires that the sender of a message can send it without knowing the exact recipient" What do you mean by "the sender of a message", is the sender the function or the programmer? – user8437463 May 7 at 8:41
  • 2
    @user8437463 I understand it such that the sender is the code which calls draw. – Angew is no longer proud of SO May 7 at 9:13
  • 3
    @user8437463 The “sender” is the code that is calling a method. – Konrad Rudolph May 7 at 9:50
33

Polymorphism is the ability to have objects of different types understanding the same message

This seems like a rather poor explanation of polymorphism to me. Technically correct but not very helpful in explaining the usefulness of it. Basically it's backwards to how polymorphism really gets used. So we can change your example to be like so:

circle1.draw();
rectangle1.render();

And it would still work just fine. This is the crux of your confusion. It's also demonstrative of why 'objects understanding the same message' is not a really helpful explanation. If I'm a circle, the fact that a rectangle might have the same method doesn't matter to me. As a circle, I only care about circle things, not stupid cornery shapes.

To understand the value of polymorphism, you need to think about the code that is calling this. I'm going to start with Python because I think this concept is a little easier to understand in that context. Consider the following method:

def paint(*shapes):
  for shape in shapes:
    shape.draw()

In this case we can pass any object to this method and as long as it has a draw() method that accepts zero parameters, it will send the 'draw' message to each thing. This is a form of polymorphism called 'duck-typing'. So your initial example could be aligned with this kind of approach. If I change the rectangle's method to render() then it will fail when a rectangle is passed in. There would no longer be a common (implicit) interface.

The potential pitfall is that not every type might understand the 'draw' message the same way. For example, if you pass in a Gunslinger object, the paint method will call the Gunslinger draw method without any problem but the meaning of the Gunslinger draw() method is very different than what is intended. In practice this problem tends to be unusual but it can happen.

Now in a language such as Java or C#, you have the concept of an explicit interface. Just having a method with the same name isn't enough. Your class needs to implement a common interface in order for a method to be 'the same message'. For example, the equivalent of the above paint method in Java would be:

void paint(Object... shapes) {
  for (Object shape : shapes) {
    shape.draw();
  }
}

But unlike the Python version this won't work. It won't even compile. The reason being that there's no draw() defined in the Object type. To fix that we need a type such as Shape that defines the draw() method. Now the method becomes:

void paint(Shape... shapes) {
  for (Shape shape : shapes) {
    shape.draw();
  }
}

And works as expected. There's still a big difference between this and the Python version: if I try to pass something in that doesn't implement Shape, I'm going to get a compile time error (or a runtime casting error.) If I try to pass in my Gunslinger object, it will no longer work. Likewise, if Circle and/or Rectangle don't implement the Shape interface, they won't be accepted either. As far as the compiler is concerned, these two are no more similar to each other than they are to the Gunslinger version if there's no common interface between them.

So in short, with this kind of typing, the 'message' of a method is not the same just because the methods have the same name (and signature), the 'message' is defined by the method definition in the interface. Without a common interface, Circle.draw() and Rectangle.draw() are just two methods that happen to have the same names but they are not considered the 'same message'

I think it's important to understand though that conceptually there's not a huge divide between the two approaches. The difference is whether the interface (or contract) is implicit in the code or explicit. Gunslinger.draw() doesn't become equivalent to Shape.draw() just because there is no compiled interface for Shape.

| improve this answer | |
  • 2
    I see a risk with your explanation, as you used "Object" as the base class, which while correct, since it's entire function in the language is based on polymorphism, isn't the best example. I recommend "Shape", this is because when I was reading your answer, I had in my mind, that polymorphism includes the subclass will masquerade as the base class when passed to the function, then I noticed you just used "Object", instead of "Shape".. I mention this because depending on how you intended, there could be room for argument there, and don't want to make the wrong assumption. – Dan Chase May 6 at 16:42
  • 2
    @DanChase I used Object to demonstrate that a paint method that doesn't know what it is getting is problematic. The example could probably use some elaboration. I was also trying to avoid dynamic/static typing since I think that can actually make this harder to understand while still satisfying the requirements of your typical curly-brace/semicolon language. But it could be JS, I suppose. I'll revise the answer when I have more time. – JimmyJames May 6 at 17:03
  • no worries and no need, You've clarified, I was just worried that I over-reacted, and it sounds like I did. – Dan Chase May 6 at 18:10
  • 1
    @DanChase It's good feedback. I'm going to revise and elaborate. – JimmyJames May 6 at 18:12
  • 1
    I tried to upvote but didn't realize I already did, so had to hit it again to correct. Great explanation that explores the differences and impacts of typing. – Dan Chase May 8 at 5:40
9

So circle1 and rectangle1 understood the same message draw() without using Polymorphism!

What makes you think they are not using Polymorphism?

Am I missing something?

Yes: that what you describe is Polymorphism, by definition.

| improve this answer | |
  • Indeed. Not explicitly defining the supertype, does not mean you're NOT using polymorphism; it only means you'll have a hard time enforcing it later on, when others tinker with your code and break the implied contract. – Duroth May 6 at 12:29
  • 11
    This is clearly a correct answer in weakly typed languages, and also in strictly typed languages when Circle and Rectangle share a common supertype. But do you think this is also valid when none of the former conditions is fulfilled? (I honestly don't know - do you?) – Doc Brown May 6 at 12:42
  • 1
    P.S. If you think of the message as it was conceptualized in the early days - a token of some sort that identifies the high-level operation - then you could say that, if you can't use the two polymorphically, the problem is that the calls aren't technically treated as the same message under the hood. E.g. say you have a vtable; in a call via obj.vptr[func_offset], you could say that func_offset is the token that identifies the message - but only if there's a guarantee that it'll mean the same for the two types. – Filip Milovanović May 6 at 14:55
  • 1
    P.P.S. Oh, and JavaScript provides a different example of the same concept - a function is really a property on the object (e.g., you can do text.toUpperCase(), but also text["toUpperCase"]()), and the runtime uses the name of the function as the message token - which is what ultimately lets you use two largely unrelated objects in a polymorphic way. – Filip Milovanović May 6 at 15:19
  • 1
    @JörgWMittag: "The OP doesn't state whether they are talking about [...]" - that's true. On the other hand, I think they are just starting to learn and don't understand polymorphism well enough to make those distinctions; they are trying to understand the quote. As everyone else, I made some assumptions regarding what the question is about, and to me, it made sense to focus on how the idea of the message plays into it, because it seemed that that's what the question revolves around. – Filip Milovanović May 7 at 12:19
3

But the above definition also apply if we don't use Polymorphism, [...] So circle1 and rectangle1 understood the same message draw() without using Polymorphism!

This is a perfect example for a popular logical fallacy. Given the premise

  A implies B

one can not conclude that

   if we have B, therefore we also must have A.   <-- WRONG

The only valid conclusion would be:

If there's no B then there's certainly also no A.

https://en.wikipedia.org/wiki/Modus_tollens

| improve this answer | |
2

Here's a great example of polymorphism. Let's say we have the following classes to represent different types of bank accounts:

  • current
  • savings
  • business
  • shared

(And let's assume each inherits from an account super class.)

Then let's say we added the following methods to each of those classes:

  • open()
  • close()
  • suspend()

That way we would know that to open a savings account on the system, we could use savings.open(). And the same for a business account: business.open().

And if we wanted to close any account we would know we could use .close(), etc. Provided the different .close() methods did what we expected them to do, and closed each of the accounts, then that would be polymorphism.

If we didn't do that, and we used differently named methods (eg. savings.delete(), shared.erase(), current.remove()) it would get very confusing. Polymorphism is a practice which makes our code more intuitive and less confusing.

So your example of circle.draw() and square.draw() is a perfect example of Polymorphism. They are shapes, and so presumably inherit from a shape class. If you didn't use polymorphism, you would have methods like circle.render() and square.create().

The fact that you've apparently created an instance of a circle class with the name "circle1" doesn't change anything.

In short: Your example IS a demonstration of Polymorphism in its most basic form.

| improve this answer | |
1

I think a key thing to remember about functions is what happens when you call them:

  • They accept 0 or more arguments
  • They have a return type
  • They could have side effects

If your interface is as simple as just implementing a single method that accepts no arguments and has no meaningful return value, many things could probably implement the interface.

interface IDrawable {
    draw: () => any;
}

Bugs Bunny drawing

Bugs and Sam both implement you interface. A deck of cards implements your interface. Howerver, adding one return type makes it quite a different story.

interface IDrawable {
    draw: () => Image;
}

If your interface stated that the implementor had to return a picture, it's likely that Bugs would be the only one that could fit into your array of drawables.

Your example is polymorphism and depending on the application, might be useful. However more contextual interfaces can help with other cases where we need specific requirements.

| improve this answer | |
  • 1
    "Bugs and Sam both implement you interface." - from a purely technical, language features–focused standpoint, that's true. But from a design standpoint, it's not, as it depends on the meaning of the operation, even though this constraint cannot be fully expressed in the language itself. If draw means "represent visually", then Sam's implementation breaks the Liskov Substitution Principle, and unlike Bugs', it will cause bugs, and/or chaos and mayhem :) But you make a good point - strive to express the meaning of the operation more closely using the language features available. – Filip Milovanović May 7 at 12:59
0

One way to easily understand, is to think of a IS_A relationship.

A Ferarri IS A Car. So is a Ford Focus.

Polymorphism is saying, you write a function, that wants a car.. regardless of whether it's a ferarri or a ford focus. That being, the definition of your class.

result = relishOverItsExoticBeauty(car);

This will also work:

result = relishOverItsExoticBeauty(ferarri);

and so will this:

result = relishOverItsExoticBeauty(fordfocus);

Another feature of polymorphism that's important (and required for the above to work in any useful way) is the ability to override methods.

Don't confuse this with OOP, it's different. Just because you make an object with properties and methods, doesn't mean it's polymorphic. Some languages do some OOP and do not do polymorphism (at least very well).

| improve this answer | |
0

Am I missing something?

Yes, draw will ideally behave in it's own way in case of circle and rectangle. That is the definition, if you see closely.

| improve this answer | |
0

What you’re thinking of is called “duck-typing,” after the old saying, “If it looks like a duck, and walks like a duck, and quacks like a duck, it’s a duck.”

There are languages that use it extensively. The C++ standard library has largely shifted from defining class hierarchies, such as the ones in <iostream>, to assembling templates that use informal “concepts.” There is, for example, no formal class all containers or all iterators inherit from. If you can dereference it, increment it and compare it, it’s an iterator. In particular, native pointers are iterators. If begin() and end() return iterators (and the end() iterator holds no valid data, and is reachable by repeatedly incrementing the begin() iterator to obtain a sequence of valid iterators, etc.) it’s a container.

This has advantages over trying to implement multiple classes at once, not least performance.

There are things you cannot do this way, however. One is implementing abstract interfaces and dynamically passing in an object whose type you do not know at compile time. You cannot link to a library that accepts generic objects that support an interface: all the code must be a template in your header file. In many languages, the compiler must create different versions of the function for every possible version of the template, instead of generic code that would run on every possible object. (Or else it must compile to a higher-level intermediate code, not native code.) It’s also likely that someone will write some method called draw() that just coincidentally happens to compile with your code. If there is a formal class (or typeclass) interface, that cannot happen by accident: the code will only be able to use that interface if the programmer claims that a type supports it.

| improve this answer | |
0

Background/Definitions

"Polymorphic" roughly translates to "multiply-shaped", and means that a single piece of code will work for many different (data/code) structures. Your code example isn't quite polymorphic, since you have one line of code for a circle and one for a rectangle; here's how we could make it truly polymorphic:

for shape in [circle1, rectangle1]:
  shape.draw()

Here the line of code shape.draw() is polymorphic, since that single line of code works for multiple data structures (a circle and a rectangle).

We can understand polymorphism in terms of "first-class functions/methods" (i.e. values which represent methods). Polymorphic code can work in multiple situations by first fetching a method which is appropriate for the current situation, then running that method.

In the above example, these two steps could be written like this:

for shape in [circle1, rectangle1]:
  myMethod = Object.lookup(shape, "draw")
  Method.invoke(myMethod)

I've made up the static methods Object.lookup and Method.invoke, but hopefully you get the idea: we're looking up the "draw" slot of the shape object, which will return a value myMethod representing that method. Since Object.lookup works the same way for any string and object it is not polymorphic. The static method Method.invoke will run myMethod; again, this works the same way for all method values, so it's also not polymorphic.

So why does OOP make such a big deal about polymorphism? There are two main reasons:

  • Some languages support polymorphism (like shape.draw()) but do not support first-class methods (like myMethod). Java and C++ were examples of such languages, although newer versions so support first-class methods (called "lambdas"). In such languages we cannot write code involving things like myMethod, so the above explanation won't work in practice (although the idea still applies).

  • The code Object.lookup(shape, "draw") is dynamic: the lookup is done at run-time, and we have no way to know if it will work (for example, instead of the literal string "draw", we could instead take the string from a file or from user input). In the polymorphic code, the method name draw is static: it's always literally there in the code, which might gives us enough information to check whether the lookup will work before we run the code. Again, Java and C++ are examples of languages which will perform these checks (as part of their compilation).

(Languages like Java and C++ have had a large impact on programming, and OOP in particular, so it's not surprising that their features and style crop up a lot when describing OOP concepts like polymorphism. Other languages, like Python, don't do such checks, which leads to different styles like "duck typing" that others mention.)

One way to check whether a lookup will work is called subtype polymorphism, where the method name is associated with some explicit 'contract' (e.g. a "class", "interface", "signature", "abstract type", "existential type", "type class", etc. depending on the language) and we only allow lookups of that method for values which claim to fulfil that contract (e.g. "objects instantiating a class/subclass", "objects inheriting from a prototype", "modules implementing a signature", "types instantiating a type class", etc.).

The Answer to Your Question

Checking for things like subtype polymorphism is conservative:

  • Code which passes the check will work. For example, if we require a Circle object and the Circle class implements a draw method, then calling draw on the given object will succeed in finding an implementation to run.
  • Code which would work might not pass the check. For example, if we try giving a Rectangle object to the code in the last example, the check will fail since it isn't a Circle. Yet, in this case, Rectangle has a draw method, so the code would work (if the failed check didn't stop us).

Your question is describing this second situation. We can think of type-checking as a form of mathematical proof: if we know that shape is a Circle, that implies is has a draw method, so we know it's safe to call shape.draw(). On the other hand, if we know that shape is not a Circle, we don't know whether it has a draw method of not, so it would be unsafe to allow calling shape.draw().

Some languages (e.g. C++ and Java) will forbid these unsafe situations, so we need to make more complicated proofs to convince them that it's safe (e.g. having Rectangle and Circle inherit draw from some parent class, or implement the same Drawable interface, etc.). Other languages (e.g. Python and Smalltalk) will allow these unsafe situations, but then it's up to us to make sure our programs work as expected.

Extra Info

Note that there are other forms of polymorphism too! For example "parametric polymorphism" describes code which doesn't care about some details of a value, and hence those details can have any type. For example, getting the length of a list:

def length(l):
  result = 0
  while l.next:
    result = result + 1
    l = l.next
  return result

This will work for lists of integers, lists of strings, lists of lists of booleans, etc. and is hence (parametrically) polymorphic.

In OOP the word "polymorphism" tends to mean subtype polymorphism, whilst parametric polymorphism tends to be called "generics". Languages which don't enforce checks, like Python, are more fuzzy when it comes to such classification: we can write Python code which uses (subtype) polymorpism (e.g. using classes), but we can also write Python code which looks like (subtype) polymorphism, without any explicit mechanism to enforce it (AKA "duck typing").

If our language supports first-class methods/functions, then we can write "higher-order functions" which act in a similar way to polymorphic code, further blurring the boundaries. For example:

def runOn(f, x):
  return f(x)

for (draw, shape) in [(drawCircle, circle1), (drawRectangle, rectangle1)]:
  runOn(draw, shape)
| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.