【Go入門】関数と無名関数

シェアする

こんにちは。Go入門ブログの第6回です。
本記事では、Goにおける関数の基本的な仕様や定義方法、使用方法などを解説します。

JavaやC#、RubyやPHPなど、現在広く使われているプログラミング言語の多くはクラスベースのオブジェクト指向を採用しています。
これに対し、Goは言語仕様としてオブジェクト指向を採用していません。
Goにおいて関数を定義し活用することは、前述のクラスベースの言語でクラスを扱うのと同等の重要性を持ちます。

関数の定義

関数の定義にはfuncステートメントを使用します。
以下はGoにおける基本的な関数定義の書式です。

func [関数名]([引数定義] 型) [戻り値の型] {
処理
}

それでは例として、引数で与えられた値を2乗するだけの簡単な関数を定義してみましょう。

// 引数で与えられた値を2乗する関数
func square(x int) int {
    return x * x
}

funcステートメント直後のsquareが関数名です。
関数名の直後に括弧でくくられているのが引数で、int型の引数xを定義しています。
引数定義の後ろに記述されたintが戻り値の型を表しています。

それ以降の{ }で括られたブロックに関数の処理を記述します。
関数の呼び出し元に値を返すにはreturnを使用します。

引数の記述

関数に複数の引数を定義する場合、以下のようにカンマ区切りで記述します。

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

上記の例は、以下のように書き換えることができます。

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

このように、同じ型の引数を定義する場合、型の指定を末尾の一箇所にまとめることができます。
引数ひとつずつに型を指定する場合と変わりませんが、可読性を考慮し型の指定をできる限りまとめる方が良いでしょう。

戻り値

戻り値を持たない関数

戻り値を持たない関数の定義は、以下の例のように戻り値の型指定を省略するだけです。

func outputString(s string) {
    fmt.Println(s)
    return
}

Javaにおけるvoidのような、戻り値がないことを示す型の使用は必要ありません。

複数の戻り値を持つ関数

Goの関数は複数の戻り値を返すことができます。
以下の例を見てください。

// 引数で与えられた2つの値を除算し商と剰余を返す関数
func divmod(x, y int) (int, int) {
    var quotient, remainder int

    // 引数の商を求める
    quotient = x / y
    // 引数の剰余を求める
    remainder = x % y
    return quotient, remainder
}

上の例の2行目にある(int, int)のように戻り値の型の指定を括弧で括り、カンマ区切りで複数の戻り値を記述します。
returnステートメントでも、型の指定に記述したのと同じ個数の戻り値をカンマ区切りで記述します。
それでは、この関数を呼び出して戻り値を受け取り、出力するサンプルをみてみましょう。

import (
    "fmt"
)

func main() {
    // divmod関数から2つの戻り値を受け取って出力する
    q, r := divmod(10, 3)
    fmt.Printf("商=%d 剰余=%d\n", q, r)
}

// 引数で与えられた2つの値を除算し商と剰余を返す関数
func divmod(x, y int) (int, int) {
    var quotient, remainder int

    // 引数の商を求める
    quotient = x / y
    // 引数の剰余を求める
    remainder = x % y

    return quotient, remainder
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
商=3 剰余=1
戻り値の破棄

複数の戻り値を持つ関数を呼び出す際、常にすべての戻り値を使用しなければならないのはやや不便です。
先の例でも、商は不要で剰余のみを求めたい場合などもあるでしょう。

そのような場合、アンダースコア(_)を使用することで戻り値の一部を破棄することができます。

import (
    "fmt"
)

func main() {
    // divmod関数から剰余のみを受け取り商は破棄する
    _, r := divmod(10, 3)
    fmt.Printf("剰余=%d\n", r)
}

// 引数で与えられた2つの値を除算し商と剰余を返す関数
func divmod(x, y int) (int, int) {
    var quotient, remainder int

    // 引数の商を求める
    quotient = x / y
    // 引数の剰余を求める
    remainder = x % y

    return quotient, remainder
}

7行目でdivmod関数を実行し、ひとつめの戻り値をアンダースコアで破棄しています。
上記のサンプルの実行結果は以下のようになります。

$ go run main.go
剰余=1

Goは使用しない変数の宣言を許容しない言語仕様ですので、不要な戻り値はきちんと破棄するようにしましょう。

名前つきの戻り値(named return value)

Goの関数では、戻り値となる変数に名前をつけることができます。
以下の例を見てください。

// 引数で与えられた2つの値を除算し商と剰余を返す関数
func divmod(x, y int) (quotient, remainder int) {
    // 引数の商を求める
    quotient = x / y
    // 引数の剰余を求める
    remainder = x % y

    return quotient, remainder
}

ここでは、先ほど(int, int)と記述していた戻り値の型指定を、(quotient, remainder int)と書き換えています。
これにより、戻り値にあらかじめquotient、remainderという変数名を割り当てています。
したがって、varステートメントによる変数定義の宣言は削除されています。

戻り値に名前を与えることで、関数内の変数宣言を短縮できるメリットがあります。
また、どの変数が戻り値となるかを明確にできる点でも可読性に貢献するでしょう。

更に、名前をつけた戻り値の変数を使うと、 returnステートメントに戻り値を指定せずに記述することができます。
これをnaked(裸の) returnステートメントと呼びます。

// 引数で与えられた2つの値を除算し商と剰余を返す関数
func divmod(x, y int) (quotient, remainder int) {
    // 引数の商を求める
    quotient := x / y
    // 引数の剰余を求める
    remainder := x % y

    // 戻り値に名前をつけている場合、戻り値を指定せずにreturnを記述できる(naked ruturn)
    return
}

ただし、長い関数でnaked ruturnステートメントを使用すると、可読性に悪影響があります。
そのような場合は、名前つきの戻り値であってもruturnステートメントに明記した方がよいでしょう。

戻り値を利用したエラー処理

Goには他言語でいうところのtry catchのような例外処理のステートメントが用意されていません。
このため、関数を実行した直後に戻り値を利用してエラーの有無を判断する例外処理の手法が一般的です。

複数の戻り値を返すことができるので、エラー判定専用の戻り値を返すようにするのが良いでしょう。
以下のような記述はGoにおけるエラーハンドリングの基本といえます。

// 任意の関数の実行
result, err := doSomeFunction()
if err != nil {
    // エラー時の処理
}

Goの例外処理については、いずれ別の記事でより詳細に解説したいと思います。

無名関数

Goには無名関数という機能が用意されています。
これまで解説してきた通常の関数とは違って、その名の通り名前を持たない関数です。

無名関数を一言で表すと、関数を値として扱うための機能であるといえます。
関数をリテラルとして記述し、変数に代入して使用したり、他の関数の引数や戻り値として使用することができます。

無名関数の定義は以下のように記述します。
関数名を指定しない点を除き、通常の関数と同じです。

func([引数定義] 型) [戻り値の型] { 処理 }

説明だけでは理解しにくいと思いますので、実際に無名関数をどのように使用するかを見ていきましょう。

無名関数の利用

次の例では、変数に代入する値として関数リテラルを記述しています。

package main

import (
    "fmt"
)

func main() {
    // 引数で与えられた2つの数値を乗算する無名関数
    multi := func(x, y int) int { return x * y }
    fmt.Println(multi(2, 3))
    fmt.Println(multi(3, 4))
    fmt.Println(multi(4, 5))
}

関数内で繰り返し行う処理を上記のように無名関数として定義すれば、コードの可読性が高まり、コードの修正も行いやすくなります。
しかし、そのような処理は汎用性の高いことが多いです。
その場合、次の例のように処理をあらかじめ通常の関数として定義しておき、定義済みの関数を変数に代入しておくとより再利用性を高くできるでしょう。

package main

import (
    "fmt"
)

func main() {
    // 変数multiに定義済みの関数multiplicateを代入する
    multi := multiplicate
    fmt.Println(multi(2, 3))
    fmt.Println(multi(3, 4))
    fmt.Println(multi(4, 5))
}

// 引数で与えられた2つの数値を乗算する関数
func multiplicate(x, y int) int{
    return x * y
}

この2つのサンプルは記述の仕方が異なるだけで、まったく同じ処理を表しています。
この例から解る通り、無名関数は通常の関数と定義の仕方が異なるだけで、本質的には同じものであるといえます。

終わりに

本記事では、Goの関数に関する基本知識について詳細な解説を行いました。
最後に無名関数の解説を行いましたが、関数を値の様に扱えるというのは重要なポイントです。
ぜひしっかりと理解して使いこなしてください。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク