Understanding Polymorphism in Go

Go being a programming language that is very easy to learn, performant, and suitable for highly scalable distributed applications, it is certainly attracting a lot of crowd towards itself.

Being developed to overcome the shortcomings of other languages, Go is designed differently from many other languages. Go has a mixed flavor of different programming languages. Even though Go is not an OOP language, the authors of Golang have made it possible to implement useful and less complex OOP patterns in golang as well.

Polymorphism is one of the basic principles of Object-Oriented Programming.

Since this is a blog on understanding Polymorphism in Go, I will explain the remaining principles of OOP. I’ll attach the links to the blogs at the bottom.

What is Polymorphism?

Other Definitions :

Polymorphism means “Same name many forms”.

Polymorphism is considered one of the important features of Object-Oriented Programming. Polymorphism allows us to perform a single action in different ways. In other words, polymorphism allows you to define one interface and have multiple implementations.

Characteristics of Polymorphism :

  1. The functionality of a method behaves differently in different scenarios.
  2. The behavior of a method depends on the data provided.
  3. It allows the same name method with different types.
  4. Polymorphism supports implicit type conversion.

How it is different in Go?

When talking in terms of programming languages like Java :

1. Runtime Polymorphism: It is a process in which a function call to the overridden method is resolved at Runtime. This type of polymorphism is achieved by Method Overriding.

Method overriding: Occurs when a derived class has a definition for one of the member functions of the base class. That base function is said to be overridden.

2. Compile time Polymorphism: In this type of polymorphism, the compiler is able to know which exact functions will be executed for a particular cal. It is also known as static polymorphism. This type of polymorphism is achieved by function overloading or operator overloading.

Method Overloading: When there are multiple functions with same name but different parameters then these functions are said to be overloaded. Functions can be overloaded by a change in the number of arguments or/and a change in the type of arguments.

When talking in terms of Go programming language :

Compile-time polymorphism is not possible in Golang. It only has Runtime Polymorphism.

Method Overloading in golang results in error. Look at the example below.

#Output from above program./method_overloading.go:11:6: Area redeclared in this block
previous declaration at ./method_overloading.go:6:36

Though there are some workarounds to implement method overloading in golang using variadic function and empty interface, I think there is a reason why the authors did not provide the functionality. So for now we are not going to discuss it.

Still, if you are too curious to control yourself from googling, click here to find the alternate way to implement method overloading.

Operator Overloading is also not supported in Golang. The reason for this is stated in faq of go - https://golang.org/doc/faq#overloading

Method dispatch is simplified if it doesn’t need to do type matching as well. Experience with other languages told us that having a variety of methods with the same name but different signatures was occasionally useful but that it could also be confusing and fragile in practice. Matching only by name and requiring consistency in the types was a major simplifying decision in Go’s type system.
Regarding operator overloading, it seems more a convenience than an absolute requirement. Again, things are simpler without it.

Runtime Polymorphism in Go :

Interfaces in golang are quite different from other languages like Java where a class has to explicitly state that it implements an interface using the implements keyword.
Go interfaces are implemented implicitly if a type provides the definition for all the methods declared in the interface.

A variable of an interface type can contain value of any type which implements the interface. This is the property that helps in achieving polymorphism in the Go language

Objects of different types are treated in a consistent way, as long as they stick to a single interface, which is the essence of polymorphism.

Let’s look at an example :

Everyone must have used the errors package from golang.
And must have also used the Error method to display the in string format.

Let’s see the contents of errors.go file in golang errors package.

The errors.go file simply has 3 things.
1. errorString type, which consists of a string typed field that would hold the error message.
2. Error method, which returns the error message string.
3. New function, has return type error interface.

// The error built-in interface type is the conventional interface // for representing an error condition, with the nil value //representing no error.type error interface {
Error() string
}

Above is the declaration of error interface in go’s builtin package.

Now, as you can see in a New function in errors.go file, it is returning errorString object. It was able to do so because, errorString object provides Error method definition, which makes it implicitly implement the error interface.

Let’s see how we generally use the error package to generate errors.

#output of above code
it's an error

Pretty simple, right!
Now if I don't want to use the same way to display errors. I want to use my own way of displaying errors. Let’s try it then.

We will look at how we can implement the same behavior in our code and see how we can define our own customized error message by defining our own Error method. And at the end how polymorphism is achieved in this particular case.

Let’s understand the example below

In the code below, we have used 2 types for structuring error messages. 1.errorString with a field s of type string for the error message.
2.MyError with field ErrorSeverity & ErrorMessage, which will hold the severity of the error and error message.

Both these types have respective Error methods in order to return error messages in different ways.

We have another function in the program, printError.
This function takes the input argument of the type error interface and calls the Error method on the passed argument resulting error message.

Based on the concrete type of the error interface, different Error() methods will be called. So, we achieved polymorphism in the printError() function.

#output of above codeDefault Error function called from errors packageError_Severity: FATAL    Error_Message: Customized Error Function called from current package with error severity

If you add another concrete type in this program that implements error interface this Error() function will print a customized error message without any change due to polymorphism.

The very first definition which we saw above, fits perfectly here.

Polymorphism means that a piece of code changes its behavior depending on the concrete data it’s operating on.

Conclusion :

Thank You!

Encapsulation: https://sagarsonwane230797.medium.com/understanding-encapsulation-in-go-ac575813c3ad

References :

github-repo: https://github.com/sagar23sj/Go-OOP/tree/main/Polymorphism

Software Engineer @Josh Software Pvt. Ltd.