長生村本郷Engineers'Blog

千葉県長生村本郷育ちのエンジニアが書いているブログ

Golang で関数のデフォルト引数を指定する

f:id:kenzo0107:20191105183003j:plain

概要

Ruby で関数のデフォルト引数を設定する場合は以下のように指定できます。

def hoge(a, b = 2)
    c = a + b
end

c = hoge(1)

p c
// c = 3

Golang だと以下の様なデフォルト引数の定義ができません。

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

こんな時にどうしようかと探っていると、 Functional Option Pattern に当たりました。

Functional Option Pattern

以下 2 記事が有名で必読です。

任意の値を定義する・しないで関数を分けると引数分、メソッドが増え、煩雑になります。

const defaultB = 2

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

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

こういった煩雑さを解決する Golang の特性を活かした解決法が Functional Option Pattern です。

Functional Option Pattern で書き換え

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
}

デフォルト引数の指定が効いているのがわかります。

Option という引数に *configs の設定を持つ関数を定義し、 その関数で各値を任意で設定することで、デフォルトの設定を上書いています。

こうすることで、 設定したい任意の値だけを設定し、その他はデフォルト値を参照する、ということができます。

よし完璧だ!と思ったら...テストでこける。。。

func main() {
    fmt.Println(reflect.DeepEqual(WithB(12), WithB(12)) // false
}

WithB は関数型が返るので、 reflect.DeepEqual では false になります。

reflect.DeepEqual のコードを見てみると関数型 (reflect.Func) で Nil でなければ、 false が返るようになってます。

コメントにある // Can't do better than this: が推して知るべし。

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

Functional Option Pattern にもう一手間加える

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
}

WithB は int 型の値を返し、 reflect.DeepEqual は true を返します。

Option を interface で定義することで、どの様な型にでも呼び出せるのを利用しつつ、 Apply メソッドを定義して、 configs の上書きを図る、という算段です。

以下 googleapis/google-api-go-client で定義されている Functional Option Pattern が素敵だったので、こちらとても参考になります。

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

まとめ

軽い気持ちで関数のデフォルト引数の設定どうやるんだろうか? と調べたら思わぬ展開になって、奥深さを感じました。

以上 ご参考になれば幸いです。

スターティングGo言語 (CodeZine BOOKS)

スターティングGo言語 (CodeZine BOOKS)

  • 作者:松尾 愛賀
  • 出版社/メーカー: 翔泳社
  • 発売日: 2016/04/15
  • メディア: 単行本(ソフトカバー)