Choosing between a function or a method? That look like a simple question.

Go defines a method as a function with a receiver. What does it mean?

How choose carefully between the two? Let’s dive into this deeper than it seems topic.

Definitions

Function (mathematics): a relation or expression involving one or more variables.

“the function (bx + c)”

Method (computer programming): A method in object-oriented programming (OOP) is a procedure associated with a message and an object. An object consists of data and behavior. The data and behavior comprise an interface, which specifies how the object may be utilized by any of the various consumers of the object.

I’d like to add a third definition to the table:

Subroutine (computer programming): a sequence of program instructions that performs a specific task, packaged as a unit According to Wikipedia: a subroutine may be called a procedure, a function, a routine, a method, or a subprogram.

To simplify, a subroutine is a group of instruction and could be either a function or a method. It doesn’t really help this far.

By mathematical definition, a function has some inputs and produce some output based on them. If the function is called several times with the same inputs the resulting outputs have to be the same. It doesn’t depend on any state. In Go, you are not forced to do so but it’s a good practice to ensure this input-output consistency. That’s maybe a track to follow.

Let’s now look at the method. Go is not OO per se, but the following part of the method definition is very interesting: “An object consists of data and behavior. The data and behavior comprise an interface, which specifies how the object may be utilized by any of the various consumers of the object.”

If you replace the word “object” by “type”, you have a perfect definition a Go method:

“A method in Go is a function associated with a type. A type consists of data and behavior. The data and behavior comprise an interface, which specifies how the object may be utilized by any of the various consumers of the type.”

In Go, methods are the implicit definition of the interface of a type.

Example

Let’s take a simple struct for a deck of cards. Each card is represented by a string containing its value and color.

type Deck struct {
    Cards []string
}
deck := Deck{Cards:[]string{
        "7H","8H","9H","10H","JH","QH","KH","AH",
        "7S","8S","9S","10S","JS","QS","KS","AS",
        "7D","8D","9D","10D","JD","QD","KD","AD",
        "7C","8C","9C","10C","JC","QC","KC","AH"}}

Now let’s imagine that you want to implement a method returning a slice of the remaining hearts cards. You choose a method because you came from an OO background and it feels like the natural way to do it.

func (d Deck) RemainingHearts() []string {
    s := []string{}
    for _, c := range d.Cards {
        if strings.Contains(c, "H") {
            s = append(s, c)
        }
    }
    return s
}

If you look carefully, the method is just using the deck as an input and provide a new slice as output. If you call it many times with the same deck, you will get the same output. This process is stateless. Does it define the behavior of a deck? No. You can shuffle a deck, you can pick a card in it. These are methods of a deck. Listing the remaining hearts in a deck is just something you can do with a deck but does not define its interface.

In this case, RemainingHearts is a good candidate for being a function instead of a method.

func RemainingHearts(d Deck) []string {
    s := []string{}
    for _, c := range d.Cards {
        if strings.Contains(c, "H") {
            s = append(s, c)
        }
    }
    return s
}
Go Playground

As discussed earlier, picking a card is part of the interface of a deck so we can implement it this way:

func (d *Deck) Pick() (card string) {
    i := rand.Intn(len(d.Cards))
    card = d.Cards[i]
    d.Cards = append(d.Cards[:i], d.Cards[i+1:]...)
    return
}

It’s perfecly legit as a method. Both the function and the method can be used as follow:

hearts := RemainingHearts(deck)
pick := deck.Pick()
Go Playground

Conclusion

If given the same inputs, an algorithm always produces the same output, it’s a good candidate to be a function. If a function defines the behavior of a type and/or depends on the state of a type, it should be a method.

More on this topic: https://flaviocopes.com/golang-methods-or-functions/