Golang 入門

Go 関数型と関数値

Go言語において、関数は至高の地位を占めています。すなわち、第一級オブジェクト (First-class citizens) です。

この言葉が持つ実質的な意味は、関数が単なる静的な呼び出しを待つだけのコードブロックではないということです。関数は通常の整数 (int) や文字列 (string) と同様に、データ(値)として扱うことができます。関数を変数 (Variable) に代入したり、他の関数に引数 (Parameter) として渡したり、さらには関数から別の関数をリターン (Return) させたりすることが可能です。

1. 関数型の深い理解

int が整数型を表すように、関数にも独自の「型」が存在します。関数型 (Function Type) は、関数のシグネチャ (Signature) によって決定されます。具体的には、「どのような型の引数を受け取り、どのような型の値を返すか」という点です。関数の名前自体は、この関数型には含まれません。

1.1 関数型の基本構文

関数型を定義する汎用的なフォーマットは以下の通りです:

func(引数の型リスト) (戻り値の型リスト)

具体的な例をいくつか見てみましょう:

シナリオ 1:基礎的な加算演算の型

2つの int を受け取り、1つの int を返す関数の型は func(int, int) int です。

package main

import "fmt"

// 通常の加算関数を定義
func add(x int, y int) int {
	return x + y
}

func main() {
	// 1. 変数 'operation' を宣言。型は func(int, int) int
	var operation func(int, int) int
		
	// 2. 'add' 関数自体(括弧なし、呼び出しではない)をこの変数に代入
	operation = add
		
	// 3. 変数名を介して、その裏側にある実際の関数を呼び出す
	result := operation(5, 3)
	fmt.Println(result) // 出力: 8
}

シナリオ 2:引数なし・戻り値なしの型

何も受け取らず、何も返さない関数(例えばメッセージをプリントするだけなど)の型は func() です。

package main

import "fmt"

func greet() {
	fmt.Println("Hello, world!")
}

func main() {
	var greeting func() // 引数なし・戻り値なしの関数変数を宣言
	greeting = greet    // 代入
	greeting()          // 実行、出力: Hello, world!
}

シナリオ 3:エラー処理を伴う多値戻り値の型

以前学んだ除算関数の場合、2つの int を受け取り、1つの int と1つの error を返します。その型は func(int, int) (int, error) です。

2. 関数値 (Function Values) の驚くべき活用法

関数を値として変数に代入できるのであれば、それらはプログラム内を自在に駆け巡ることができます。これにより、非常に強力なプログラミングパターンが生まれます。

2.1 関数を引数として渡す (コールバック関数 Callback)

ある関数を別の関数に渡し、受け取った側が特定のタイミングでそれを実行させることができます。これはカスタムソーティング (Sorting)イベントリスナー (Event Listener)、または非同期タスクの処理において非常に一般的です。

package main

import "fmt"

// calculate 関数は3つの引数を受け取る:2つの数値、および「計算ルールを定義した」関数 op
func calculate(x int, y int, op func(int, int) int) int {
	return op(x, y) // 内部で渡されたロジックを実行
}

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

func main() {
    // 異なる計算ロジックを動的に注入
	sum := calculate(5, 3, add)
	difference := calculate(5, 3, subtract)
		
	fmt.Println("加算結果:", sum)       // 出力: 加算結果: 8
	fmt.Println("減算結果:", difference) // 出力: 減算結果: 2
}

2.2 関数を返す関数 (ジェネレーター / クロージャファクトリ)

クロージャの章で見た通り、これは「関数を製造する工場」のようなものです。

package main

import "fmt"

// multiplier は倍率 factor を受け取り、掛け算を専門に行う関数を返す
func multiplier(factor int) func(int) int {
	return func(x int) int {
		return x * factor
	}
}

func main() {
	double := multiplier(2) // 「2倍にする」関数を製造
	triple := multiplier(3) // 「3倍にする」関数を製造
		
	fmt.Println("5 の2倍:", double(5)) // 出力: 10
	fmt.Println("5 の3倍:", triple(5)) // 出力: 15
}

3. 実戦ケーススタディ

関数型の威力をより現実のエンジニアリングに近い例で感じてみましょう。

3.1 type キーワードによるシグネチャの簡略化 (ストラテジーパターン)

関数シグネチャが長い(例:func(int, int) int)場合や頻繁に使用される場合、コードが煩雑になります。type キーワードを使用して、このシグネチャに短いエイリアス (Alias) を付けることができます。これはストラテジーパターン (Strategy Pattern) を実装する際に非常に有効です。

package main

import "fmt"

// 特定のシグネチャを持つ関数に 'Operation' というエイリアスを付ける
type Operation func(int, int) int

// 関数シグネチャが非常にスッキリする
func calculate(x int, y int, op Operation) int {
	return op(x, y)
}

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

func main() {
	// 異なる関数をマップ (Map) に格納し、動的なルーティングを実現
	operations := map[string]Operation{
		"add":      add,
		"multiply": multiply,
	} 

	x, y := 10, 5
	fmt.Printf("%d + %d = %d\n", x, y, calculate(x, y, operations["add"]))      	
	fmt.Printf("%d * %d = %d\n", x, y, calculate(x, y, operations["multiply"])) 
}

3.2 カスタムソーティングロジック

Go標準ライブラリの sort.Slice は、関数引数の威力を完璧に体現しています。どのようにソートすべきかを知る必要はなく、ただ「比較ルール」を定義した関数を渡すだけです。

package main

import (
	"fmt"
	"sort"
)

func main() {
	numbers := []int{5, 2, 8, 1, 9, 4} 

	// 匿名関数を渡し、昇順ルールを定義 (前の数が後ろの数より小さければ true を返す)
	sort.Slice(numbers, func(i, j int) bool {
		return numbers[i] < numbers[j]
	})
	fmt.Println("昇順ソート:", numbers) // 出力: [1 2 4 5 8 9] 

	// 別の匿名関数を渡せば、瞬時に降順ルールへ変更可能
	sort.Slice(numbers, func(i, j int) bool {
		return numbers[i] > numbers[j]
	})
	fmt.Println("降順ソート:", numbers) // 出力: [9 8 5 4 2 1]
}

3.3 Web開発におけるミドルウェア (Middleware) パターン

Go言語の Webフレームワーク (Web Framework) において、「ミドルウェア」はほぼすべて高階関数 (Higher-order functions) で実装されています。核心的な思想は、コアとなる処理関数を受け取り、その外側にロジック(ログ記録、認証など)を包み込み(ラップし)、新しくパッケージングされた関数を返すというものです。

package main

import "fmt"

// HTTPコアハンドラ関数の型を定義
type HttpHandler func(request string) string

// ミドルウェアの型を定義:Handler を受け取り、新しい Handler を返す
type Middleware func(HttpHandler) HttpHandler

// ログ記録ミドルウェア
func logger(next HttpHandler) HttpHandler {
	return func(request string) string {
		fmt.Println(">>> リクエスト受信:", request) // リクエスト到達時の前置ロジック
		response := next(request)                // コアロジックの呼び出し
		fmt.Println("<<< レスポンス送信:", response) // 処理完了後の後置ロジック
		return response
	}
}

// 最終的なコアビジネスロジック
func mainHandler(request string) string {
	return "Hello, あなたのリクエスト内容は " + request
}

func main() {
	// logger ミドルウェアで mainHandler をラップする
	wrappedHandler := logger(mainHandler) 

	// ラップされた後の関数を実行
	wrappedHandler("ユーザーデータの照会")
}

実行結果は、ミドルウェアがコアロジックの前後でインターセプト(割り込み処理)を行う能力を完璧に示しています。