フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

GoでClosureと戯れてみる

Closure(クロージャ) とは、関数が実行された時にその静的スコープで定義された変数を利用できる関数です。

使ったコード - framinal-new-tools/go-closure at main · lirlia/framinal-new-tools · GitHub

こんなコードを書いてみると、counter1 を呼び出すたびにインクリメントされます。これは getCounter() クロージャの変数の静的スコープにある count 変数を保持し続けているのでうまいこと利用できています。

package main

import "fmt"

func main() {

    counter1 := getCounter()

    fmt.Println(counter1()) // 1
    fmt.Println(counter1()) // 2
}

func getCounter() func () int{
    count  := 0

    return func() int {
        count++
        return count
    }
}

同じ関数を別に呼ぶと

同じ関数を呼び出し counter1 / counter2 で分けてみます。 また getCounter 関数の呼び出し時に count 値によって返す関数を変えてみます。

package main

import "fmt"

func main() {

    counter1 := getCounter()
    counter2 := getCounter()

    fmt.Println(counter1()) // 1
    fmt.Println(counter1()) // 2
 
    fmt.Println(counter2()) // 1
    fmt.Println(counter2()) // 2
    fmt.Println(counter2()) // 3
}

func getCounter() func () int{
    count  := 0

    if count == 0 {
        return func() int {
            count++
            return count
        }
    }

    return func() int {
        count += 2
        return count
    }
}

これを実行すると以下の値が返ってきます。

1
2
1
2
3

このことから、counter1counter2 変数にはこの関数と、count の山椒をもちづつけていることがわかります。

       return func() int {
            count++
            return count
        }

同じ関数で複数のクロージャを作る

getCounter2() で二つのクロージャを返して実行してみます。

package main

import "fmt"

func main() {

    counter3, counter4 := getCounter2()

    fmt.Println(counter3()) // 1
    fmt.Println(counter3()) // 2
    fmt.Println(counter4()) // 3
    fmt.Println(counter4()) // 4
}

func getCounter2() (f1 func () int, f2 func () int){
    count := 0

    f1 = func() int {
            count++
            return count
        }

    f2 = func() int {
        count++
        return count
    }

    return f1, f2
}

これを実行すると以下の値が返ってきます。

1
2
3
4

これは f1()f2() が count 変数の参照を共有しているためですね。(このケースでは mutex をとって排他制御をする必要がある)

ポインタをクロージャに渡す

func main() {

    i := 0
    counter5:= getCounter3(&i)
    fmt.Println(counter5()) // 1
    i++
    fmt.Println(counter5()) // 3
}

func getCounter3(count *int) func () int{
    return func() int {
        *count++
        return *count
    }
}

このケースでは以下の値が返ってきます。

1
3

ポインタを渡しているので、別のところでインクリメントすればそれがそのまま反映されますね。このケースでもポインタをそのまま渡すのではなく mutex を含めて渡す必要がありますね。

type value struct {
    i int
    mu sync.Mutex
}