Catalogue
Specifying Default Argument Values for Functions in Golang

Specifying Default Argument Values for Functions in Golang

🌐 日本語で読む

Overview

In Ruby, you can set default argument values for a function like this:

1
2
3
4
5
6
7
8
def hoge(a, b = 2)
c = a + b
end

c = hoge(1)

p c
// c = 3

In Golang, however, you cannot define default arguments like the following:

1
2
3
4
func hoge(a, b = 2 int) string {
c := a + b
return c
}

While looking into how to handle this, I came across the Functional Option Pattern.

Functional Option Pattern

The following two articles are well-known and essential reading:

If you split functions based on whether an optional value is set or not, the number of methods grows with each argument, which gets messy.

1
2
3
4
5
6
7
8
9
const defaultB = 2

func Hoge(a int) int {
return a + defaultB
}

func HogeWithB(a, b int) int {
return a + b
}

The Functional Option Pattern is a solution that takes advantage of Golang’s characteristics to resolve this kind of clutter.

Rewriting with the Functional Option Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

const (
defaultB = 2
)

type configs struct {
b int
}

type Option func(c *configs)

func WithB(v int) Option {
return func(c *configs) {
c.b = v
}
}

var args *configs

func init() {
args = &configs{
b: defaultB,
}
}

func Hoge(a int, options ...Option) int {
for _, option := range options {
option(args)
}

c := a + args.b

return c
}

func main() {
c := Hoge(1)
fmt.Println(c) // 3

d := Hoge(1, WithB(9))
fmt.Println(d) // 10
}

You can see that the default argument is working as intended.

We define Option as a function that holds settings for *configs, and by optionally setting each value within that function, we override the default settings.

This way, you can set only the optional values you want to configure, and everything else falls back to the default values.

Just when I thought it was perfect… the tests failed.

1
fmt.Println(reflect.DeepEqual(WithB(12), WithB(12)) // false

Since WithB returns a function type, reflect.DeepEqual returns false.

Looking at the code for reflect.DeepEqual, it returns false when the value is a function type (reflect.Func) and is not Nil.

The comment // Can't do better than this: says it all.

https://github.com/golang/go/blob/master/src/reflect/deepequal.go#L126-L131

Adding One More Touch to the Functional Option Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const (
defaultB = 2
)

type configs struct {
b int
}

type Option interface {
Apply(*configs)
}

type B int

func (o B) Apply(c *configs) {
c.b = int(o)
}
func WithB(v int) B {
return B(v)
}

var args *configs

func init() {
args = &configs{
b: defaultB,
}
}

func Hoge(a int, options ...Option) int {
for _, option := range options {
option.Apply(args)
}

c := a + args.b

return c
}

func main() {
c := Hoge(1)
fmt.Println(c) // 3

d := Hoge(1, WithB(9))
fmt.Println(d) // 10

fmt.Println(reflect.DeepEqual(WithB(12), WithB(12))) // true
}

Now WithB returns an int-type value, and reflect.DeepEqual returns true.

The trick is to define Option as an interface, taking advantage of the fact that it can be called on any type, while defining an Apply method to overwrite configs.

The Functional Option Pattern defined in googleapis/google-api-go-client below is wonderful, so it’s a great reference.

https://github.com/googleapis/google-api-go-client/blob/master/option/option.go

Summary

I casually started looking into how to set default arguments for a function, and it took an unexpected turn that made me appreciate just how deep this topic goes.

That’s all.
I hope you find this helpful.

Specifying Default Argument Values for Functions in Golang

https://kenzo0107.github.io/en/2019/11/11/golang/

Author

Kenzo Tanaka

Posted on

2019-11-11

Licensed under