RichyHBM

Software engineer with a focus on game development and scalable backend development

'Method Overloading' in Go

GoLang is an interesting language in that it is an imperative language by all accounts, yet because of how it is build and handles composition, it essentially allows you to write Object-Oriented code with very little difference to how you would in say C++.

One difference however is method overloading, unlike with other languages, GoLang doesn’t support overloading methods. The main reason for this is simplification, it is both simpler for the compiler to not have to worry about different argument types on the same method, and also for the developer writing code to not have to worry about multiple signatures of the same method.

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. https://golang.org/doc/faq#overloading

Whilst method overloading is something I do find useful, I also agree that anything that can make things simpler is worth doing, even if it’s a slight inconvenience. The great thing about Go is that it actually does let you do this, or at least hide the fact it’s not available, albeit in a slightly hacky way.

Just to make things clear I don’t think you should actually do this, not when adding a slightly more descriptive name to a method given its signature doesn’t cost you anything, however I do want to illustrate one way you could do this if so inclined.

In Go interfaces work a little different to how they do in other languages, like elsewhere they specify methods that implementors must implement however you don’t strictly need to be an implementor of an interface in order to satisfy that interface, as long as you have a method that satisfies the interface you can consider your struct to be compatible with the interface.

package main

import "fmt"

type Animal interface {
    Speak() string
}

type Dog struct { }

func (d Dog) Speak() string {
    return "Woof!"
}

func main() {
	   var dog Dog
	   fmt.Println(dog.Speak())
}

Because of this, you can use an empty interface as a base for all structs, because all structs satisfy the requirement of not implementing any specific method. This is often used as a way of specifying a parameter without knowing what it should be, or rather a method that accepts any parameter, and that is exactly what we will use for our overloading. By using an empty interface as our parameter we have a one parameter function that can take in any type.

package main

import "fmt"

func Foo(foo interface{}) {
}

func main() {

}

The next step is allowing multiple parameters of different types, and this is where another feature of Go comes to the rescue. Variadic parameters allow us to take in an array of 0 - Many of a type, including calling a function without passing it any parameters.

package main

import "fmt"

func main() {
	   fmt.Println(Sum())                 // Prints 0
	   fmt.Println(Sum(1, 2))             // Prints 3
	   fmt.Println(Sum(1, 2, 3, 4))       // Prints 10
	   fmt.Println(Sum(7, 1, -2, 0, 18))  // Prints 24
}

func Sum(numbers ...int) int {
    n := 0
    for _,number := range numbers {
        n += number
    }
    return n
}

So by this point we have a way of taking in 1 parameter of any type, and a way of taking N amount of parameters of a type. By combining both methods we are left with a way of taking in any amount of parameters of any types. In fact, this is what Println does in order to take in any amount of arguments.

package main

import "fmt"

func main() {
    Print()                  // Prints nothing
    Print(1, 2)              // Prints 12
    Print(1, 2, 3, 4)        // Prints 1234
    Print(7, 1, -2, 0, 18)   // Prints 71-2018
}

func Print(vals ...interface{}) {
    for _,val := range vals {
        fmt.Print(val)
    }
    fmt.Println()
}

By now all that is missing to our method is a way to perform different logic based on the given parameters. This can be solved by both a type switch and different logic based on the size of the array.

Say we want a sum and print method, this will take at most 3 parameters and add these, if all parameters are numbers it will sum their value and print the result, otherwise it will concatenate them and print the result.

package main

import (
    "fmt"
    "errors"
    "strconv"
)

func isInt(i interface{}) bool {
    switch i.(type) {
        case int: 	return true
        default: 	return false
    }
}

func sumAndPrint(vals ...interface{}) error {
    if len(vals) > 3 {
        return errors.New("Too many parameters")
    }

    nonNumber := false
    sum := 0
    concat := ""

    for _,val := range vals {
        if isInt(val) == false {
            nonNumber = true
            concat += val.(string) + " "
        } else {
            concat += strconv.Itoa(val.(int)) + " "
            sum += val.(int)
        }
    }

    if nonNumber {
        fmt.Println(concat)
    } else {
        fmt.Println(sum)
    }
    return nil
}

func main() {
    sumAndPrint()                  //Prints 0
    sumAndPrint(1)                 //Prints 1
    sumAndPrint(1, 2)              //Prints 3
    sumAndPrint(1, 2, 3)           //Prints 6
    sumAndPrint("Hi")              //Prints Hi
    sumAndPrint("Hello", "World")  //Prints Hello World
    sumAndPrint("Hi", 1, 2)        //Prints Hi 1 2

    if err := sumAndPrint(1,2,3,4); err != nil {
        fmt.Println(err)           //Prints Too many parameters
    }
}

And there you have it, a method that can take an arbitrary amount of parameters of different types, not quite method overloading but comparable in terms of the user of the method.

And just to be clear, please don’t do this. Just name your methods something slightly different!

Like what I do?