技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

クロージャ【Go】

クロージャとは、関数と関数内で参照する変数をまとめた機能のことです。
引数で指定されない変数を、内部に保持する事ができます。
Goでは、返り値に無名関数を使用して実現します。

func incrementGenerator() func() int {
    x := 0    // 内部で使用する変数を、代入時に1度だけ初期化
    return func() int {
        x++
        return x
    }
}

func main() {
    counter := incrementGenerator()    // x := 0と無名関数の代入が行われる
    fmt.Println(counter())    // 1  無名関数の内部の処理だけが行われる
    fmt.Println(counter())    // 2  同上
}


また、クロージャの返り値を複数にして、以下のようにリセット用関数を返すこともできます。

func incrementGenerator() (func() int, func()) {
    x := 0    // 内部で使用する変数を代入時に1度だけ初期化
    gen := func() int {
        x++
        return x
    }
    reset := func() {
        x = 0
    }
    return gen, reset
}

func main() {
    counter1, reset1 := incrementGenerator()    // x := 0と無名関数の代入が行われる
    counter2, _ := incrementGenerator()    // x := 0と無名関数の代入が行われる。resetは使用しないため破棄

    fmt.Println(counter1())    // 1
    fmt.Println(counter1())    // 2
    fmt.Println(counter2())    // 1
    reset1()
    fmt.Println(counter2())    // 2  counter2のxはreset1()の影響を受けない
    fmt.Println(counter1())    // 1
}

クロージャの変数は、他クロージャの変更による影響を受けません。


クロージャは、異なる条件を保存して計算するときに便利です。
例えば消費税率を保存して計算するために、次のようできます。
(切り捨てなどは考慮していません)

func bill(tax float64) func(total float64) float64 {
    return func(total float64) float64 {
        return total * (1 + tax)
    }
}

func main() {
    price8 := bill(0.08)
    fmt.Println(price8(100))    // 108
    fmt.Println(price8(200))    // 216

    price5 := bill(0.05)
    fmt.Println(price5(100))    // 105
    fmt.Println(price5(200))    // 210
}



次のようにクロージャの引数に関数を用いると、
さらに複雑なパターン分けに対応する事ができます。

func sliceCalc(f func(x int) int) func([]int) []int {
    return func(ary []int) []int {    // 短縮のため、ここで変数名aryを記述
        buff := make([]int, len(ary))    // 結果を格納するスライスの作成
        for i, v := range ary {    // for rangeは配列やスライスのloopを簡単にできる。indexとvalueを返す
            buff[i] = f(v)    // スライスの各要素を引数の関数で計算し、結果格納用スライスに格納
        }
        return buff
    }
}

func square(x int) int{
    return x * x
}

func x2(x int) int {
    return x * 2
}

func main() {
    a := []int {1, 2, 3, 4, 5}

    squareSlice := sliceCalc(square)
    x2Slice := sliceCalc(x2)
    fmt.Println(squareSlice(a))    // [1 4 9 16 25]
    fmt.Println(x2Slice(a))    // [2 4 6 8 10]

    fmt.Println(sliceCalc(square)(a))    // この様にも書ける
    fmt.Println(sliceCalc(x2)(a))    // この様にも書ける
}

好ましいかどうかは状況次第ですが、引数とする関数内で分岐すれば、パターン分けは広がります。


参考 www.geocities.jp