Go言語 関数

Go言語は信頼性が高く、スケーラブルでシンプルなソフトウェアを構築するために設計された、オープンソースのプログラミング言語です。

Go言語の重要な特徴の一つは、関数の使用です。Goでは、関数は第一級関数です。つまり、変数に代入したり、引数として渡したり、他の関数から返したりすることができます。

Go言語 関数の定義

Goの関数は、キーワードfuncに続いて関数名、引数、データ型、関数の戻り値のデータ型、関数本体を記述します。

func add(x int, y int) int {
    return x + y
}

この例では、add関数は整数型の引数x,yを受け取り、整数型x+yを返します。

上記のように同じ引数の型が連続するような場合は、次のように型をまとめて書くこともできます。

func add(x, y int) int {
    return x + y
}

Go言語 複数の戻り値

Goの関数は複数の値を返すことができます。これは、一回の関数呼び出しで複数の結果を返したい場合に便利です。

func calculate(a int, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff
}

この例では、関数calculateは2つの整数の引数aとbを取り、2つの引数の合計と差の2つの値を返します。

Go言語 関数の実行

関数を実行するには、関数名の後に必要な引数を括弧で囲んで呼び出します。

package main

import "fmt"

func calculate(a int, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff
}

func main() {
    x, y := calculate(5, 3)
    fmt.Println(x, y)
}

このコードは、2つの整数aとbを入力とし、その和と差を計算し、結果を2つの整数のタプルとして返す関数calculateを実装したGoプログラムを定義しています。Println関数を使用して、xとyの値を表示します。実行すると、このコードは5と3の和と差である8 2を出力します。

関数から戻り値を受け取りたくない場合は、識別子_を使用することができます。_は、代入文の中で値を無視したい場合に使用できる特別なプレースホルダーです。例えば、2つの値を返す関数から最初の戻り値だけを受け取りたい場合、2番目の戻り値には_を使用することができます。

package main

import "fmt"

func calculate(a int, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff
}

func main() {
    x, _ := calculate(5, 3)
    fmt.Println(x)
}

Goでは、関数が値を返し、その値が変数に代入される場合、その変数はコード内で使用されなければならず、さもなければコンパイラはその値が使用されていないことを示すエラーを生成します。これは、リソースの浪費を避けるためと、コード内のすべての変数に目的があることを確認するためです。

しかし、返された値を使用する必要がなく、それでもコンパイラのエラーを回避したい場合は、値を無視することを示すプレースホルダとして空白の識別子_を使用することができます。Goコンパイラは、未使用の変数が_識別子に割り当てられていれば、エラーを発生させません。

Go言語 無名関数

無名関数は、名前付きの識別子を持ちません。通常、他の関数に引数として渡したり、変数に代入したりする必要がある場合に、その場で作成、実行されます。

package main

import "fmt"

func main() {
    add := func(a, b int) int {
        return a + b
    }

    sum := add(5, 10)
    fmt.Println("The sum is", sum)
}

この例では、無名関数func(a, b int) intが変数addに代入されています。この無名関数は2つのint型引数aとbを受け取り、それらの合計を返します。main関数はこの無名関数を呼び出し、その結果を変数sumに格納し、コンソールに表示します。

無名関数は、高階関数(1つ以上の関数を引数として受け取り、結果として関数を返す関数)の中でよく使われます。無名関数はこのような状況で構文を単純化し、コードを読みやすくすることができます。

無名関数は一度だけ呼び出すことができ、一度定義すると名前を付けて参照することはできないことに注意する必要があります。

package main

import "fmt"

func apply(op func(int, int) int, a, b int) int {
    return op(a, b)
}

func main() {
    add := func(a, b int) int {
        return a + b
    }

    subtract := func(a, b int) int {
        return a - b
    }

    result := apply(add, 5, 10)
    fmt.Println("The result of adding 5 and 10 is", result)

    result = apply(subtract, 5, 10)
    fmt.Println("The result of subtracting 10 from 5 is", result)
}

この例では,apply関数は2つのint型引数a,bと関数opを引数として受け取る高階関数です。opは、2つのint引数をとり、int結果を返す関数です。apply関数はop関数を引数a,bで呼び出し、その結果を返します。

main関数は、2つの無名関数addとsubtractを定義しています。そして、main関数はopの引数としてadd関数とsubtract関数に加え、引数5と10を渡してapply関数を呼び出します。apply関数はadd関数とsubtract関数の結果を返し、コンソールに表示されます。

以下のように関数が関数を返す形も可能です。

package main

import "fmt"

func getOperator(op string) func(int, int) int {
    switch op {
    case "+":
        return func(a, b int) int {
            return a + b
        }
    case "-":
        return func(a, b int) int {
            return a - b
        }
    case "*":
        return func(a, b int) int {
            return a * b
        }
    case "/":
        return func(a, b int) int {
            return a / b
        }
    default:
        return nil
    }
}

func main() {
    add := getOperator("+")
    subtract := getOperator("-")
    multiply := getOperator("*")
    divide := getOperator("/")

    fmt.Println("5 + 10 =", add(5, 10))
    fmt.Println("5 - 10 =", subtract(5, 10))
    fmt.Println("5 * 10 =", multiply(5, 10))
    fmt.Println("5 / 10 =", divide(5, 10))
}

この例では、getOperator関数は文字列の引数opを受け取り、単純な算術演算を行う関数を返します。getOperator関数はswitch文を使って、opの値からどの演算を返すかを決定しています。

main関数はgetOperator関数を4回呼び、その結果を変数add, subtract, multiply, divideに代入しています。次にmain関数は、返された関数をそれぞれ引数5と10で呼び、結果をコンソールに表示します。

例えば、変数の型としても関数を使用することができます。上の例を型宣言した変数に代入してみましょう。

package main

import "fmt"

func getOperator(op string) func(int, int) int {
    switch op {
    case "+":
        return func(a, b int) int {
            return a + b
        }
    case "-":
        return func(a, b int) int {
            return a - b
        }
    default:
        return nil
    }
}

func main() {
    var add func(int, int) int
    add = getOperator("+")
    fmt.Println("5 + 10 =", add(5, 10))

    var subtract func(int, int) int
    subtract = getOperator("-")
    fmt.Println("5 - 10 =", subtract(5, 10))
}

この例では、getOperator関数は、int型の引数を2つ取り、int型の結果を返す関数を返しています。意図的に変数の定義で関数の型を宣言し、結果を変数に格納しています。この場合、型はfunc(int, int) intと宣言されています。

このように、関数も型として扱うことができるので、他の基本型と同じような使い方ができます。

Go言語 クロージャ

クロージャとは、外側の関数が返された後でも、そのスコープ内の変数にアクセスできる関数値のことです。言い換えれば、クロージャは、関数が実行を終えた後でも、その周囲の環境を記憶しておくことができます。

package main

import "fmt"

func incrementer() func() int {
    var x int
    return func() int {
        x++
        return x
    }
}

func main() {
    inc := incrementer()
    fmt.Println(inc()) // 1
    fmt.Println(inc()) // 2
    fmt.Println(inc()) // 3
}

関数incrementerは変数xをインクリメントして返す無名関数を返しています。

返された無名関数は変数incに格納されます。incが存在する限りオブジェクトが保持され、ローカル変数xの値が保持されてます。incが呼ばれるたびにxが増加し、その値が返されるので、プログラムの出力は1, 2, 3となります。

この記事を書いた人

著者の画像

Jeffry Alvarado

Ex-Facebook Engineer 大学ではコンピュータサイエンスを専攻し、在学中に複数のインターンシップを経験。コンピュータサイエンスが学習できるプラットフォームRecursionを創業し、CTOとしてカリキュラム作成、ソフトウェア開発を担当。


ツイート