Skip to content

第 10 章: 高階関数と関数合成

10.1 ファーストクラス関数

Go では関数はファーストクラスオブジェクトです。関数を変数に代入したり、引数として渡したり、戻り値として返したりできます。

関数型の定義

Go では func キーワードで関数型を定義できます。

// 関数型の定義
type Predicate func(FizzBuzzValue) bool
type Mapper func(FizzBuzzValue) string

テスト: 関数を変数に代入する

func TestPredicate_Fizzを判定する(t *testing.T) {
    isFizz := func(v FizzBuzzValue) bool {
        return v.Value() == "Fizz"
    }

    v := NewFizzBuzzValue(3, "Fizz")
    if !isFizz(v) {
        t.Fatal("isFizz should return true for Fizz")
    }
}

テスト: 関数を引数として渡す

func TestFizzBuzzList_Filter_Fizzだけを抽出する(t *testing.T) {
    fbt := FizzBuzzType01{}
    cmd := NewFizzBuzzListCommand(fbt, 15)
    list := cmd.Execute().(*FizzBuzzList)

    isFizz := func(v FizzBuzzValue) bool {
        return v.Value() == "Fizz"
    }
    filtered := list.Filter(isFizz)

    for _, v := range filtered.Value() {
        if v.Value() != "Fizz" {
            t.Fatalf("expected Fizz, got %q", v.Value())
        }
    }
}
実装コード: Filter メソッド
// Predicate は FizzBuzzValue を受け取り bool を返す関数型です。
type Predicate func(FizzBuzzValue) bool

// Filter は条件に合致する要素だけを含む新しいリストを返します。
func (l *FizzBuzzList) Filter(pred Predicate) *FizzBuzzList {
    var result []FizzBuzzValue
    for _, v := range l.value {
        if pred(v) {
            result = append(result, v)
        }
    }
    return &FizzBuzzList{value: result}
}

10.2 クロージャ

クロージャは外側のスコープの変数をキャプチャした関数です。Go のクロージャは変数への参照をキャプチャします。

テスト: クロージャで述語関数を生成する

func TestMakeValuePredicate_指定した値と一致する述語を返す(t *testing.T) {
    isFizz := MakeValuePredicate("Fizz")
    isBuzz := MakeValuePredicate("Buzz")

    v := NewFizzBuzzValue(3, "Fizz")
    if !isFizz(v) {
        t.Fatal("isFizz should return true")
    }
    if isBuzz(v) {
        t.Fatal("isBuzz should return false for Fizz")
    }
}
実装コード: MakeValuePredicate
// MakeValuePredicate は指定した値と一致するかを判定する述語関数を返します。
func MakeValuePredicate(target string) Predicate {
    return func(v FizzBuzzValue) bool {
        return v.Value() == target
    }
}

テスト: クロージャで Map 関数を適用する

func TestFizzBuzzList_Map_値を変換する(t *testing.T) {
    values := []FizzBuzzValue{
        NewFizzBuzzValue(1, "1"),
        NewFizzBuzzValue(3, "Fizz"),
    }
    list := NewFizzBuzzList(values)

    toUpper := func(v FizzBuzzValue) string {
        return strings.ToUpper(v.Value())
    }
    got := list.Map(toUpper)

    if got[0] != "1" || got[1] != "FIZZ" {
        t.Fatalf("Map result = %v", got)
    }
}
実装コード: Map メソッド
// Mapper は FizzBuzzValue を受け取り string を返す関数型です。
type Mapper func(FizzBuzzValue) string

// Map は各要素に関数を適用した結果のスライスを返します。
func (l *FizzBuzzList) Map(fn Mapper) []string {
    result := make([]string, len(l.value))
    for i, v := range l.value {
        result[i] = fn(v)
    }
    return result
}

10.3 関数合成

Go には演算子レベルの関数合成構文はありませんが、高階関数を使って手動で合成できます。

テスト: 2 つの関数を合成する

func TestCompose_2つの関数を合成する(t *testing.T) {
    double := func(n int) int { return n * 2 }
    addOne := func(n int) int { return n + 1 }

    doubleThenAddOne := Compose(double, addOne)

    got := doubleThenAddOne(5)
    if got != 11 {
        t.Fatalf("Compose(double, addOne)(5) = %d, want 11", got)
    }
}
実装コード: Compose 関数
// Compose は f を適用した後に g を適用する合成関数を返します。
func Compose(f, g func(int) int) func(int) int {
    return func(n int) int {
        return g(f(n))
    }
}

テスト: Filter と Map を組み合わせる

func TestFizzBuzzList_FilterとMapを組み合わせる(t *testing.T) {
    fbt := FizzBuzzType01{}
    cmd := NewFizzBuzzListCommand(fbt, 15)
    list := cmd.Execute().(*FizzBuzzList)

    isFizz := MakeValuePredicate("Fizz")
    getValue := func(v FizzBuzzValue) string { return v.Value() }

    result := list.Filter(isFizz).Map(getValue)

    for _, s := range result {
        if s != "Fizz" {
            t.Fatalf("expected Fizz, got %q", s)
        }
    }
}

10.4 各言語の高階関数比較

機能 Go Java Ruby TypeScript
関数型 func(T) R Function<T,R> Proc / Lambda (t: T) => R
クロージャ func リテラル Lambda 式 ブロック / Lambda アロー関数
関数合成 手動実装 andThen / compose >> / << 手動実装
フィルタ 手動 for ループ Stream.filter select Array.filter
マップ 手動 for ループ Stream.map map Array.map

Go はジェネリクス以前はスライス操作を for ループで明示的に書く必要がありました。Go 1.18+ のジェネリクスにより、型安全な汎用関数を書けるようになりました(第 12 章で詳述)。

10.5 まとめ

本章では以下を学びました:

  • ファーストクラス関数: 関数を変数・引数・戻り値として扱える
  • 関数型: type Predicate func(FizzBuzzValue) bool のように型を定義
  • クロージャ: 外側のスコープの変数をキャプチャする関数
  • 関数合成: 高階関数で手動実装
  • Filter / Map: FizzBuzzList に関数型メソッドを追加

Go のシンプルな関数型サポートは、Java の Function<T,R> や Ruby の Lambda と比較すると構文は少ないですが、同じパターンを明示的に実装できます。