Wednesday, June 8, 2011

Polymorphism in Go


Sometimes I’ve found it easier to explain polymorphism with an illustration. An illustration attempts to simplify and has its drawbacks, but it could help clarify the idea. Assume a martian came to earth and he knows about humans but he doesn’t know about the difference between a man and a woman. He now approaches random humans on the street and asks the same question, "Human, tell me what you like to do". Assume now that the first human the martian approached with this question is a Man. To go by a western stereotype, his response could be "I like fishing". Now assume it approached a Woman with the same question. Continuing with stereotypes, her response could be "I like shopping". As far as the martian sees it, it appears that he has asked only a Human, but he got a different answer each time. The answer depended on the actual type of the person he asked. Silly example might be, but this is similar to polymorphism - where different responses are obtained from the same type, in this case Human, depending on what specific type it is.

In Go, we use interfaces to achieve this polymorphism. For a starter tutorial on interfaces in Go, please read this tutorial: Interfaces in Go. If we created an interface and had other types implement that interface, then we can reach into each of those types via the defined methods of the interface without having to know what the specific type was. Let’s just implement that and see what we get.

Full code
package main

import "fmt"

type Human interface {
    myStereotype() string
}

type Man struct {
}

func (m Man) myStereotype() string {
    return "I'm going fishing."
}

type Woman struct {
}

func (m Woman) myStereotype() string {
    return "I'm going shopping."
}
func main() {
    m := new (Man)
    w := new (Woman)

    //an array of Humans - we don’t know whether Man or Woman
    hArr := [...]Human{m, w} //array of 2 Humans. One is the type Man, one is the type Woman.
    for n, _ := range (hArr) {

        fmt.Println("I'm a human, and my stereotype is: ", hArr[n].myStereotype())   //appears as human type, but behavior changes depending on actual instance
        
    }

}


I'm a human, and my stereotype is: I'm going fishing.
I'm a human, and my stereotype is: I'm going shopping.

In the above code within the for loop, we are able to ask for the stereotype of a human, but get different responses each time based on what is the intrinsic type, whether Man or Woman. At the point of calling, we only know that it is a Human - so it appears that the human is morphing each time. There you go, polymorphism!

I hope that example was easy to understand and to illustrate the concept. Polymorphism, at least for me, has been a difficult concept to explain, which is why I resort to easier examples. But if you think about it, the above example could have in reality be implemented as if Man and Woman were inheriting from Human. So let’s extend the example a bit further.

We shall this time change the interface to Hobby. This time we shall have Man and Woman derived from Human. Additionally we will also add a Dog that does not derive from any other type. But we will have each of them implement the interface Hobby. Since they do that, we should be able to call the myStereotype() method on all of them and receive different results based on the intrinsic type.

Full code
package main

import "fmt"

type Hobby interface {
    myStereotype() string
}

type Human struct {

}

func (h Human) myStereotype() string {
    return "I'm a Human, only an abstract concept, and I can have no hobby."
}

type Man struct {
    Human //anonymous class to inherit Human behavior
}

func (m Man) myStereotype() string {
    return "I'm a Man and I'm going fishing."
}

type Woman struct {
    Human //anonymous class to inherit Human behavior
}

func (m Woman) myStereotype() string {
    return "I'm a Woman and I'm going shopping."
}

type Dog struct {
    //does not inherit any other type
}

func (m Dog) myStereotype() string {
    return "bow bow bow, I'm chasing sticks."
}

func main() {
    h := new (Human)
    m := new (Man)
    w := new (Woman)
    d := new (Dog)

    //an array of hobby instances - we don’t need to know whether human or dog
    hobbyArr := [...]Hobby{h, m, w, d} //array of 3 Humans and 1 dog.
    for n, _ := range (hobbyArr ) {

        fmt.Println("My hobby?  Well,", hobbyArr [n].myStereotype())  //appears as Hobby type, but behavior changes depending on actual instance

    }
}

My hobby? Well, I'm a Human, only an abstract concept, and I can have no hobby.
My hobby? Well, I'm a Man and I'm going fishing.
My hobby? Well, I'm a Woman and I'm going shopping.
My hobby? Well, bow bow bow, I'm chasing sticks.



12 comments:

  1. The goal of polymorphism is to be able to alter an already working code with a class and substitute this class by a derived one without touching any code but the constructor and the base methods definition to add virtuality.

    With GO you cant do this, you have to know in advance you're going to specialise the base class and never reference its type but an interface that may someday be usefull.
    Ending by coding an interface for any class and any of it's uses due to the fear of having to specialise it someday is clumsy.

    This way any GO API should take in parameters only interfaces in order to support polymorphism.
    I feel it quite bad, maybe i'm wrong...

    ReplyDelete
    Replies
    1. Exactly. This is not polymorphism. And definitely interfaces{} are not made to substitute polymorphism. Trying to use them to hack 'polymorphism' into the code is a misuse IMHO - the language is not made for supporting polymorphism, and it is by design, not because Pike or Thompson do not know what it is. Better to learn living without this concept.

      Delete
  2. And in GO the base class in it's own code never calls the derived methods since the base class refer itsself by its type not by an interface.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Anonymous said "With GO you cant do this, you have to know in advance you're going to specialise the base class?"

    No you don't, that was the point of this demo.
    If you need an interface, you can put one in later, and it works.

    You are talking about subclassing, which is also done differently in GO, but it works as you would want it to anyway.

    Give an example on HOW this would mess things up. What is it you want to do?

    ReplyDelete
  5. you could simplify your loop there....like so:

    // use only the index
    for n := range (hArr) {

    fmt.Println("I'm a human, and my stereotype is: ", hArr[n].myStereotype())

    }

    // OR
    // use only the value
    for _, h := range (hArr) {

    fmt.Println("I'm a human, and my stereotype is: ", h.myStereotype())

    }

    ReplyDelete
  6. These seem like trivial examples. I'm grateful for their simplicity, however, I'm interested in small complicated cases... like an http client request. This is more complicated because the remote server has separate state; the client may have expired cookies which might require a subsequent login...

    ReplyDelete
  7. Struct Human and Struct Dog are exactly the same, but it makes a perfect sense as there might be any number of branching.

    ReplyDelete
  8. Hm .. woman-goes-shopping-man-goes-fishing stereotype, that's probably political incorrect ;-)

    Go's way of expressing polymorphism looks to me like a well thought orthogonal concept. It does not suffer of the problematic issues of inheritance/multiple inheritance and keeps stuff simple and organized. Awesome and simple.

    ReplyDelete
  9. Indeed it looks like methods in base classes cannot call methods in derived classes. That's a dealbreaker for me. Maybe I goofed somewhere, here is what how I tested: http://ideone.com/kUx8yp. Output is "go by n/a", sadly...

    ReplyDelete
    Replies
    1. You can call methods of "subtypes" from the base type provided you are willing to initialize a function reference in the base type with a method value derived from the subtype. In the constructor of the "subtype", you initialise the member variable of the base type with a method value that refers to the method defined by the subtype. When the function assigned to the variable is invoked, the subtype's method will be invoked with the receiver initialized as one might expect from virtual function dispatch

      See https://golang.org/ref/spec#Method_values.

      In essence, you can get a virtual function table in go that you would get in C++ or Java but, admittedly, the programmer does have to manage it herself. In practice, a go programmer would probably avoid this technique where possible, however, if the problem demands it, this solution is available to be used.

      Delete
  10. @Jean-Louis Leroy
    of course not, go automatically useses role and they are inside they stay inside.

    dont think of it as classes, think of it as boxes, like box inside another box.

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.