Functions vs Methods: explain reasoning behind STD

239 views
Skip to first unread message

Deividas Petraitis

unread,
Jun 2, 2022, 10:38:37 AM6/2/22
to golang-nuts
Background: when I am writing code mostly I end up with some clunky solutions I am not happy with and one of the reasons I think is that I might be mixing funcs vs methods.

So the question is basically the title.

I have searched both in this group ( without a luck )  and in the Internet for possible ideas/answers and I end up with following points:

1. Structs are for representing things, they are things
2. Funcs are for performing actions, calculating things
3. Methods for performing actions on thing state
4. Methods to satisfy interface

Based on these points, let's have a "thing":

```
type Person struct {
   weight float32 // pounds
   height float32 // inches
}
```

And calculate BMI:

```
func CalculateBMI(weight float32 height float32) float32 {
 return weight / (height * height) x 703
}

p := Person{157, 66}

CalculateBMI(p.weight, p.height)
```

However if I would want for example to implement some kind of interface I would be forced to use method over func, but it looks weird to me for some reason:

```
interface BMICalculator {
    CalculateBMI() float32
}

// Satisfies BMICalculator
func (p * Person) CalculateBMI() float32 {
 return p.weight / (p.height * p.height) x 703
}
```

For example when looking at this STD code I am not longer to reason about choices funcs vs methods based on points above: https://cs.opensource.google/go/go/+/master:src/net/tcpsock_posix.go;l=26;drc=8a56c7742d96c8ef8e8dcecaf3d1c0e9f022f708

Why `(a *TCPAddrfamily()` is implemented as a method when it can be implemented as pure func like:

``` 
func Family(a *TCPAddr) int {
   if a == nil || len(a.IP) <= IPv4len {
       return syscall.AF_INET
   }
   if a.IP.To4() != nil {
       return syscall.AF_INET
   }
   return syscall.AF_INET6
}
```

So using reasoning above based on silly example looks O.K. but I seems that I might be still missing some kind of context/information because I can't explain example from STD.

I would like to see what other people approaches this "problem" and to learn is there any other rules/guidelines to follow.

Cheers, Deividas

Axel Wagner

unread,
Jun 2, 2022, 10:51:15 AM6/2/22
to Deividas Petraitis, golang-nuts
On Thu, Jun 2, 2022 at 4:38 PM Deividas Petraitis <h...@deividaspetraitis.lt> wrote:
1. Structs are for representing things, they are things
2. Funcs are for performing actions, calculating things
3. Methods for performing actions on thing state
4. Methods to satisfy interface

4 is really the *big* technical point. 3 is basically just aesthetics.
 
However if I would want for example to implement some kind of interface I would be forced to use method over func, but it looks weird to me for some reason: 

```
interface BMICalculator {
    CalculateBMI() float32
}

// Satisfies BMICalculator
func (p * Person) CalculateBMI() float32 {
 return p.weight / (p.height * p.height) x 703
}
```

What looks weird about this? Seems pretty straight forward.
Leaving aside whether that's a sensible use of interfaces, but assuming there is such an interface…
 

For example when looking at this STD code I am not longer to reason about choices funcs vs methods based on points above: https://cs.opensource.google/go/go/+/master:src/net/tcpsock_posix.go;l=26;drc=8a56c7742d96c8ef8e8dcecaf3d1c0e9f022f708

Why `(a *TCPAddrfamily()` is implemented as a method when it can be implemented as pure func like:

Aesthetics. It's code that is specific to that type. By making it a method, it makes the encapsulation clear. You can enforce a convention that only methods access unexported fields of a type directly. And it splits up the namespace. If multiple types have a `family` method, you don't have to overload that name as `tcpFamily`, `udpFamily`… but instead tie the name to the type without having it in the package scope.

But yes, there is no *technical* reason why you couldn't make it a function. The language makes no distinction there, except for interface satisfaction.
 

``` 
func Family(a *TCPAddr) int {
   if a == nil || len(a.IP) <= IPv4len {
       return syscall.AF_INET
   }
   if a.IP.To4() != nil {
       return syscall.AF_INET
   }
   return syscall.AF_INET6
}
```

So using reasoning above based on silly example looks O.K. but I seems that I might be still missing some kind of context/information because I can't explain example from STD.

I would like to see what other people approaches this "problem" and to learn is there any other rules/guidelines to follow.

Cheers, Deividas

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/b4424465-ea36-4863-8ccf-3631de4ab56an%40googlegroups.com.

Jan Mercl

unread,
Jun 2, 2022, 11:04:39 AM6/2/22
to Deividas Petraitis, golang-nuts
On Thu, Jun 2, 2022 at 4:38 PM Deividas Petraitis
<h...@deividaspetraitis.lt> wrote:

> 1. Structs are for representing things, they are things

Forget about structs. Methods are attached to types. Structs are just
one of the many kinds of types. Assuming structs only does not provide
the full picture one needs to think about methods vs functions.

> 2. Funcs are for performing actions, calculating things
> 3. Methods for performing actions on thing state

Types do not necessarily have any state (think of zero sized types).
Yet they may still be very useful. Another reason why to get rid of
the assumption "thing" == struct.

> 4. Methods to satisfy interface

-j

Jesper Louis Andersen

unread,
Jun 2, 2022, 1:24:07 PM6/2/22
to Deividas Petraitis, golang-nuts
On Thu, Jun 2, 2022 at 4:38 PM Deividas Petraitis <h...@deividaspetraitis.lt> wrote:
Background: when I am writing code mostly I end up with some clunky solutions I am not happy with and one of the reasons I think is that I might be mixing funcs vs methods.


Squint your eyes enough, and methods are just a special kind of function. One where a specific parameter is "implicit", "environmental", "contextual", or "given."

The key insight is that methods are bound to types, as Jan writes. The second insight is that they let the type vary. Imagine you had something like

    var a *TcpAddr
    ...
    family(a)

vs

    var a *TcpAddr
    ...
    a.family()

If you change the `var a...` line to a new type, the former code also has to change to a different function call, whereas the latter code would work as long as the new type implements a `family()` method. Interfaces makes this idea more viable, and empowers the reader and compiler further.

Aside from compiler technicalities, it's also a great classifier tool. We should write programs in a dialogue with the compiler, and the compiler should guide us to what the valid programs are. A method is a strong hint that a computation belongs to a given type, and should be considered in the context of said type. This allows humans and compilers to filter the set of applicable calls down to the method set in the given context, which is often a fraction of all possibilities.

--
J.

Sean Liao

unread,
Jun 2, 2022, 2:49:43 PM6/2/22
to golang-nuts
Namespacing, aesthetics, and interfaces
If, for whatever reason, you don't like the way method calls look, you can use:

    var a net.TCPAddr
    _ = a.Family() // usual method call
    _ = net.TCPAddr.Family(&a) // equivalent to above

If everything was a pure function, it'd look much like the second version anyway, especially if there are multiple types that expose similar functionality


- sean


atd...@gmail.com

unread,
Jun 2, 2022, 4:05:36 PM6/2/22
to golang-nuts

A short way to see it for me is to reason in terms of capabilities.
What is an instance of a given type supposed to be able to do? (method) versus what am I able to do with or upon a given instance (function).
It's not always clear though but it can help.

Deividas Petraitis

unread,
Jun 3, 2022, 2:21:43 AM6/3/22
to golang-nuts
I am really happy with thoughts and points shared so far, thank you all! Unfortunately I am not able to edit original my post, so to make sure I got all of these points I will just sum up everything below,  I think  this summary may be valuable for any less experienced gopher, please correct me in case I misunderstood something:

1. Types are for representing things, they are things
2. Funcs are for performing actions, calculating things
3. Methods identifies that computation belongs to a given type and should be considered in the context of given type

var a net.TCPAddr
var b net.UDPAddr

a.family() // family computation belongs to TCPAddr and is done in a context of TCPAddr
b.family() // family computation belongs to UDPAddr and is done in a context of UDPAddr

4. Methods let type vary and are great classifier tool ( interfaces )

type Family interface {
   family() int
}

function ComputationBasedOnFamily(f Family, otherparams int...) {
     // ...
}

5. Methods for namespacing, aesthetics

var a net.TCPAddr
var b net.UDPAddr

tcpAddrFamily(a)
tcpAddrFamily(b)

vs
var a net.TCPAddr
var b net.UDPAddr

a.family()
b.family() 

-

Deividas

Henry

unread,
Jun 3, 2022, 11:25:55 PM6/3/22
to golang-nuts
I think after a while people tend to come up with their own best practices when using Go. Each person has their own preference. There is really no right or wrong here. The argument for method vs function usually comes down to the domain and which makes more sense. 

For my own 'rules', I prefer to keep only the essential methods to my types and leave the rest of functionalities to functions. The reason is because Go has no inheritance, and functions allow better reusability across types without needing inheritance. 

Reply all
Reply to author
Forward
0 new messages