覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - 無名関数

前回のエントリでGo言語の関数の基本的な部分について書きました。

本エントリでは、前回のエントリでは扱わなかった無名関数について書きます。


Go言語では関数を値として扱うことができて、変数へ格納することもできます。
まずその辺りの動作を以下確認しておきます。

例えば以下のような、引数で与えられた文字列を装飾して戻り値で返す関数があったとします

func decorate(src string) string {
    return "<<" + src + ">>"
}

この場合には、decorate関数を変数に格納してその変数を経由して実行することができます

func main() {
    f := decorate   // 関数を変数に格納
    ret := f("golang")  // 変数fに対して引数を渡して関数を実行

    fmt.Println("decorete result ->", ret)  // ⇒ decorete result -> <<golang>>
}


無名関数を扱う

先の例では明確に関数名を持った関数を定義していますが、
関数名を与えずに無名関数として関数を定義して変数にセットすることができます

func main() {
    f := func(src string) string { return "<<" + src + ">>" }   // 無名関数を定義
    ret := f("clang")  // 変数fに対して引数を渡して関数を実行

    fmt.Println("decorete result ->", ret)  // ⇒ decorete result -> <<clang>>
    fmt.Printf("function type -> %T\n", f)
}

上記は変数fに対して、"string型の引数をとりstring型の戻り値を変えす無名関数" を代入しています。
変数にセットする関数の内容を記載している右辺部分は関数リテラルとなります。関数リテラルは以下のように記述します

func([引数]) [戻り値の型] { [関数の本体] }


ちなみに上記プログラムでは、今回定義した無名関数の型についてもチェックしていますが結果は以下のようになっています

function type -> func(string) string


戻り値として関数を返す関数

Go言語では 関数を戻り値として返す関数 を定義することができます。
例えば、文字列を装飾する関数を返す関数(returnDecoreteFunc)を以下のように記述することができます

func returnDecoreteFunc() func(string) string {
    return func(src string) string { return "||" + src + "||" }
}

returnDecoreteFuncの定義から、戻り値の型は「引数がstringで戻り値もstring の関数」となっています。


returnDecoreteFuncの戻り値で返された関数を実行するには以下のようになります。
一度変数に受けてその変数経由実行することも、変数を経由せずに実行することもできます。

// 一度変数に受ける場合
f := returnDecoreteFunc()
ret1 := f("Java")
fmt.Println("decorete result ->", ret1)  // ⇒ decorete result -> ||Java||

// 変数を経由しない場合
ret2 := returnDecoreteFunc()("C#")
fmt.Println("decorete result ->", ret2)  // ⇒ decorete result -> ||C#||


関数を引数にとる関数

Go言語では 関数を引数にとる関数 を定義することもできます。

例えば以下は、第1引数に文字列、第2引数に装飾した文字列を返す関数 をとる関数(decorateAndPrint)で、
第1引数の文字列を第2引数の関数で装飾して、その結果を標準出力に書きだす処理をします。

func decorateAndPrint(str string, decoreteStrategy func(string) string) {
    decoratedStr := decoreteStrategy(str)
    fmt.Printf("original string: %s -> decorated string: %s\n", str, decoratedStr)
}


以下は本関数を呼び出す例となります。
decorateAndPrintを2回呼び出していますが、2回とも第1引数は同じですが、第2引数では各々異なる処理(文字列の装飾方法)

func main() {
    decorateAndPrint("Taro", func(src string) string {
        return "///" + src + "///"
    })

    decorateAndPrint("Taro", func(src string) string {
        return "<h2>" + src + "</h2>"
    })
}

実行結果は以下の通りです。呼び出し毎に事なる装飾関数が適用されていることが分かります

original string: Taro -> decorated string: ///Taro///
original string: Taro -> decorated string: <h2>Taro</h2>


クロージャ

Goの関数は クロージャ(closure)です。
関数と関数の処理に関連する関数の外の環境をセットにして閉じ込めた状態で取り扱うことができます。

以下の例ではsumCalc関数は、「引数にintを1つとり戻り値で2つのint値を返す関数」を戻り値にする関数となっています。 戻り値で返される関数は、引数で与えられた整数値を加算していくもので、合計値をsum変数に格納しています

package main

import "fmt"

func main() {
    f := sumCalc()

    for i := 1; i < 5; i++ {
        sumVal, srcVal := f(i)
        fmt.Printf("add value: %d -> sum: %d\n", srcVal, sumVal)
    }
}

func sumCalc() func(int) (int, int) {
    sum := 0 // 足し算の合計値を格納する変数
    return func(x int) (int, int) {
        sum = sum + x
        return sum, x
    }
}

実行すると以下の通りとなります

add value: 1 -> sum: 1
add value: 2 -> sum: 3
add value: 3 -> sum: 6
add value: 4 -> sum: 10

sum変数は見かけ上は関数内のローカル変数ですが、実際にはクロージャに属する変数として利用されます。
クロージャによって束縛された変数の領域は、何らかの形でクロージャが参照され続ける限りは破棄されることがありません。
そのため、本例では関数呼び出し毎の合計値をsum変数に保持することができています



関連エントリ