Python to Go: Object Oriented to Data Oriented Thinking

Writing software with a particular programming language for a while is like living with the one. The more you live together the more you know each other and also comfortable with each other. In the way, you kind of except the limitation, short coming and weirdness of that person. You also know the weakness and find a way to handle that.

But in the way you suddenly start to like someone and start to inspect all the new weirdness. Now its the same again discovering same thing (idioms, function, control flow, data structures, design patterns) from the beginning but in a different or complicated new way and you are sometime frustrated and tired of overcoming all these new difficulties. But at the end you have to leave past behind and move forward.

That’s enough love-hate story, lets jump in. I am going to assume you are experienced enough Python Data Structure, Object Orientation. Lets start with a classic example. You are the writing a Package called shapes in python. By giving the values (initializes) of different attribute the API provide the functionality to calculate different useful results like area, perimeter of the shape etc.

The classic way to do that in python is to create a base class (Ex. Shape) and create other shapes based on the base which will inherit the base class and override the methods of base to adjust its own methods. Here I am taking in Python but any Object Oriented language can be replaced by Python.

class Shape:
"""Some doc string."""
name = "Generic Shape"
width = 0
height = 0

def area(self):
"""Some doc string."""
return None

def perimeter(self):
"""Some doc string."""
return None

Here is a shape class Now we can create a Triangle and Rectangle class based on Shape and override the area according to the formula of Triangle and Rectangle.

class Rectangle(Shape):
"""Some doc string."""
name = "Rectangle"

def __init__(self, width: float, height: float):
"""Some doc String."""
self.width = width
self.height = height

def area(self):
"""Some doc String."""
return self.width * self.height

def __repr__(self):
return f"A {self.name} of width: {self.width} and height:{self.height}"


class Triangle(Shape):
"""Some doc string."""
name = "Triangle"

def __init__(self, width: float, height: float):
"""Some doc String."""
self.width = width
self.height = height

def area(self):
"""Some doc String."""
return (self.width * self.height)/2

def __repr__(self):
return f"A {self.name} of width: {self.width} and height:{self.height}"

I am deliberately not mentioning Polymorphism, Encapsulation, Method signature this kind of words because we do not want to study those here.

Now lets see when designing the above app in python what kind of thinking we have gone through:

  • Object (Ex. Triangle, Rectangle)
  • Instances of Objects to hold data(Ex. Width, Height)
  • Methods of Objects to manipulated data(Ex. Area)
  • Inherit One Object to another(Though Still it would work without Inheritance in our current example but it will be helpful for further extension of the app.)

So here we can see every thing is Object Oriented.

I am amusing the reader is comfortable enough about Python example and I will go very detail in Go example. So in Go first thing we need to think about the data structure of Triangle and Rectangle:

type triangle struct {
width float64
height float64
}

type rectangle struct {
width float64
height float64
}

So here we can see have define User Defined Type (UDT) called triangle and rectangle . type <name> struct {} will be signature of UDT. As in Python underlying Methods such Area belongs to Object here the methods will belongs to the UDT:

func (t triangle) area() float64 {
return (t.width * t.height) / 2
}

func (r rectangle) area() float64 {
return r.width * r.height
}

Here (t triangle) tells go that that method is only accessible by any value of type triangle. So the function signature is func (identifier <typename> <function_name>() <returned_type>{} . The identifier makes a shorthand to use the attributes of a type inside of functions. In that point we can create a triangle and a rectangle to find its area:

tri := triangle{3, 4}
fmt.Printf("The area of the Triangle %v", tri.area())

We have defined a tri value of type triangle and get the area. The same holds for rectangle. We are using Printf from the fmt package to print the formatted result in console.

But we can do something better. What if we can create a link between triangle and rectangle with a generic type shape and shape will have a method called area, So that when we will provide a triangle the underlying area method will be use and when it’s a rectangle it will use the method from rectangle:

type shape interface {
area() float64
}

Here is how we can link the both shape and that is called use of interface. Interface says that:

“Who ever has that method area() with type float64 is my type too and I will take care of it.”

So in our example now any value or rectangle and triangle will also be considered as shape. Now we can attach more methods in to shape interface and use them as generic methods:

func info(s shape) {
fmt.Printf("A %T of of area %v.\n", s, s.area())
}

The following function take a shape type and use the area() methods return nothing just print the result. So when there will be a triangle it will use area() method signature from triangle and when there will be a rectangle it will use area() method signature from rectangle. Here is the whole Implementation, You can put it into a .go file and run it by yourself:

package main

import "fmt"

type triangle struct {
width float64
height float64
}

type rectangle struct {
width float64
height float64
}

func (t triangle) area() float64 {
return (t.width * t.height) / 2
}

func (r rectangle) area() float64 {
return r.width * r.height
}

type shape interface {
area() float64
}

func info(s shape) {
fmt.Printf("A %T of of area %v.\n", s, s.area())
}

func main() {
rect := rectangle{3, 4}
tri := triangle{3, 4}
// Accessing area from triangle itself
fmt.Printf("The area of the Triangle %v", tri.area())
// Accessing Area using Interface
// Info can access triangle because of interface has area()
info(rect)
//Info can access rectangle because of interface has area()
info(tri)

}

So have you found the difference of design thinking? In Go:

  • Data Type signatures and Data Structure comes first in design
  • Then Methods of the Type comes in action
  • If we think we can link different types in to one signature/interface we implement the interface to simplify that link which is part of Polymorphism

We are not going for that debate which is better language. but If you already decided that you are moving forward with go and leaving python behind, you must need to start a new way of thinking.

“Think like a Gopher, think Data Orientation first.”

References:

  1. https://gobyexample.com/interfaces
  2. https://www.udemy.com/course/learn-how-to-code/

Data Science and Data Analytics Developer