Go言語入門:言語仕様 -Vol.26-

スポンサーリンク
Go言語入門:言語仕様 -Vol.26- 用語解説
Go言語入門:言語仕様 -Vol.26-
この記事は約19分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

Function types(関数型)

関数型は、同じパラメータ型と結果型を持つすべての関数の集合を示します。関数型の初期化されていない変数の値はnilです。

FunctionType  = "func" Signature .
Signature     = Parameters [ Result ] .
Result        = Parameters | Type .
Parameters    = "(" [ ParameterList [ "," ] ] ")" .
ParameterList = ParameterDecl { "," ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ "..." ] Type .

パラメータまたは結果のリスト内では、名前(IdentifierList)はすべて存在するか、すべて存在しないかのいずれかでなければなりません。存在する場合、各名前は指定された型の1つの項目(パラメータまたは結果)を表し、シグネチャ内のすべての非空白名は一意でなければなりません。存在しない場合、各型はその型の1つの項目を表します。パラメータと結果のリストは常に括弧で囲まれますが、名前のない結果が正確に1つだけの場合は、括弧なしの型として書くことができます。

関数シグネチャの最後の入力パラメータは、...で前置きされた型を持つ場合があります。そのようなパラメータを持つ関数は可変長引数関数と呼ばれ、そのパラメータに対して0個以上の引数で呼び出すことができます。

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

解説

関数型とは何か?

関数型(function type) は、関数そのものを値として扱える型です。関数を変数に代入したり、引数として渡したり、戻り値として返したりできます。

たとえ話: 関数型は「レシピカード」のようなものです。料理の手順(関数)が書かれたカードを、本に挟んだり(変数に代入)、友達に渡したり(引数)、コピーして返したり(戻り値)できます。

package main

import "fmt"

func main() {
    // 関数を変数に代入
    var greet func(string) string
    
    greet = func(name string) string {
        return "こんにちは、" + name + "さん"
    }
    
    // 関数を実行
    message := greet("太郎")
    fmt.Println(message) // こんにちは、太郎さん
}

1. 関数型の基本

関数シグネチャ

関数型は、シグネチャ(signature) によって定義されます。

package main

import "fmt"

func main() {
    // 引数なし、戻り値なし
    var f1 func()
    
    // 引数あり、戻り値あり
    var f2 func(int) int
    
    // 複数の引数と戻り値
    var f3 func(int, int) (int, int)
    
    // 初期化されていない関数型はnil
    fmt.Println(f1 == nil) // true
    fmt.Println(f2 == nil) // true
    fmt.Println(f3 == nil) // true
}

関数の代入

package main

import "fmt"

// 通常の関数
func add(a, b int) int {
    return a + b
}

func main() {
    // 関数を変数に代入
    var f func(int, int) int
    f = add
    
    result := f(10, 20)
    fmt.Println(result) // 30
}

2. 様々な関数型

引数なし、戻り値なし

package main

import "fmt"

func main() {
    var f func()
    
    f = func() {
        fmt.Println("Hello, World!")
    }
    
    f() // Hello, World!
}

引数あり、戻り値あり

package main

import "fmt"

func main() {
    // 1つの引数、1つの戻り値
    var square func(int) int
    square = func(x int) int {
        return x * x
    }
    
    fmt.Println(square(5)) // 25
    
    // 複数の引数、1つの戻り値
    var add func(int, int) int
    add = func(a, b int) int {
        return a + b
    }
    
    fmt.Println(add(10, 20)) // 30
}

複数の戻り値

package main

import "fmt"

func main() {
    // 複数の戻り値
    var divmod func(int, int) (int, int)
    
    divmod = func(a, b int) (int, int) {
        return a / b, a % b
    }
    
    quotient, remainder := divmod(10, 3)
    fmt.Println(quotient, remainder) // 3 1
}

名前付き戻り値

package main

import "fmt"

func main() {
    var calc func(int, int) (sum int, diff int)
    
    calc = func(a, b int) (sum int, diff int) {
        sum = a + b
        diff = a - b
        return // 名前付き戻り値は自動的に返される
    }
    
    s, d := calc(10, 5)
    fmt.Println(s, d) // 15 5
}

3. パラメータ名の省略

パラメータ名は省略可能

package main

import "fmt"

func main() {
    // パラメータ名あり
    var f1 func(x int, y int) int
    
    // パラメータ名なし(型だけ)
    var f2 func(int, int) int
    
    // どちらも同じ型
    f1 = func(a, b int) int {
        return a + b
    }
    
    f2 = func(a, b int) int {
        return a * b
    }
    
    fmt.Println(f1(3, 4)) // 7
    fmt.Println(f2(3, 4)) // 12
}

空白識別子の使用

package main

import "fmt"

// パラメータを使わない場合は_を使う
func process(data []int, _ int, threshold float32) bool {
    // 2番目の引数は使わない
    return float32(len(data)) > threshold
}

func main() {
    data := []int{1, 2, 3, 4, 5}
    result := process(data, 0, 3.0)
    fmt.Println(result) // true
}

4. 可変長引数(Variadic functions)

最後のパラメータに...を付けると、可変長引数になります。

可変長引数の基本

package main

import "fmt"

func main() {
    var sum func(string, ...int) int
    
    sum = func(prefix string, numbers ...int) int {
        total := 0
        for _, n := range numbers {
            total += n
        }
        fmt.Println(prefix, total)
        return total
    }
    
    // 任意の個数の引数を渡せる
    sum("合計:", 1, 2, 3)           // 合計: 6
    sum("結果:", 10, 20, 30, 40)   // 結果: 100
    sum("空:")                      // 空: 0
}

スライスを展開して渡す

package main

import "fmt"

func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

func main() {
    // 通常の呼び出し
    result1 := sum(1, 2, 3, 4, 5)
    fmt.Println(result1) // 15
    
    // スライスを展開して渡す
    numbers := []int{10, 20, 30}
    result2 := sum(numbers...)
    fmt.Println(result2) // 60
}

interface{}を使った汎用的な可変長引数

package main

import "fmt"

func printAll(values ...interface{}) {
    for i, v := range values {
        fmt.Printf("%d: %v (%T)\n", i, v, v)
    }
}

func main() {
    printAll(1, "Hello", 3.14, true)
    // 0: 1 (int)
    // 1: Hello (string)
    // 2: 3.14 (float64)
    // 3: true (bool)
}

5. 高階関数

関数を引数として受け取ったり、関数を返したりする関数です。

関数を引数として受け取る

package main

import "fmt"

// 関数を引数として受け取る
func apply(f func(int) int, value int) int {
    return f(value)
}

func main() {
    // 二乗する関数
    square := func(x int) int {
        return x * x
    }
    
    // 2倍にする関数
    double := func(x int) int {
        return x * 2
    }
    
    fmt.Println(apply(square, 5)) // 25
    fmt.Println(apply(double, 5)) // 10
}

関数を戻り値として返す

package main

import "fmt"

// 関数を返す関数
func makeMultiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

func main() {
    double := makeMultiplier(2)
    triple := makeMultiplier(3)
    
    fmt.Println(double(5)) // 10
    fmt.Println(triple(5)) // 15
}

6. クロージャ(Closure)

関数が外側の変数を「記憶」する機能です。

基本的なクロージャ

package main

import "fmt"

func makeCounter() func() int {
    count := 0
    
    return func() int {
        count++
        return count
    }
}

func main() {
    counter1 := makeCounter()
    counter2 := makeCounter()
    
    fmt.Println(counter1()) // 1
    fmt.Println(counter1()) // 2
    fmt.Println(counter1()) // 3
    
    fmt.Println(counter2()) // 1 (別のカウンター)
    fmt.Println(counter2()) // 2
}

クロージャの実用例

package main

import "fmt"

func adder(base int) func(int) int {
    return func(x int) int {
        return base + x
    }
}

func main() {
    add10 := adder(10)
    add100 := adder(100)
    
    fmt.Println(add10(5))   // 15
    fmt.Println(add100(5))  // 105
}

7. 実用例

例1: フィルター関数

package main

import "fmt"

func filter(numbers []int, predicate func(int) bool) []int {
    result := []int{}
    for _, n := range numbers {
        if predicate(n) {
            result = append(result, n)
        }
    }
    return result
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    // 偶数だけ抽出
    evens := filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    
    // 5より大きい数だけ抽出
    greaterThan5 := filter(numbers, func(n int) bool {
        return n > 5
    })
    
    fmt.Println(evens)         // [2 4 6 8 10]
    fmt.Println(greaterThan5)  // [6 7 8 9 10]
}

例2: マップ関数

package main

import "fmt"

func mapInts(numbers []int, transform func(int) int) []int {
    result := make([]int, len(numbers))
    for i, n := range numbers {
        result[i] = transform(n)
    }
    return result
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 各要素を2倍に
    doubled := mapInts(numbers, func(n int) int {
        return n * 2
    })
    
    // 各要素を二乗に
    squared := mapInts(numbers, func(n int) int {
        return n * n
    })
    
    fmt.Println(doubled) // [2 4 6 8 10]
    fmt.Println(squared) // [1 4 9 16 25]
}

例3: リダイース関数

package main

import "fmt"

func reduce(numbers []int, initial int, accumulator func(int, int) int) int {
    result := initial
    for _, n := range numbers {
        result = accumulator(result, n)
    }
    return result
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 合計
    sum := reduce(numbers, 0, func(acc, n int) int {
        return acc + n
    })
    
    // 積
    product := reduce(numbers, 1, func(acc, n int) int {
        return acc * n
    })
    
    fmt.Println(sum)     // 15
    fmt.Println(product) // 120
}

例4: デコレータパターン

package main

import (
    "fmt"
    "time"
)

// 関数の実行時間を測定するデコレータ
func measureTime(f func()) func() {
    return func() {
        start := time.Now()
        f()
        elapsed := time.Since(start)
        fmt.Printf("実行時間: %v\n", elapsed)
    }
}

func heavyTask() {
    time.Sleep(1 * time.Second)
    fmt.Println("重い処理が完了")
}

func main() {
    // デコレートされた関数
    measured := measureTime(heavyTask)
    
    measured()
    // 重い処理が完了
    // 実行時間: 1.000...s
}

例5: コールバック関数

package main

import "fmt"

type Processor struct {
    onSuccess func(string)
    onError   func(error)
}

func (p *Processor) Process(data string) {
    if data == "" {
        if p.onError != nil {
            p.onError(fmt.Errorf("データが空です"))
        }
        return
    }
    
    if p.onSuccess != nil {
        p.onSuccess(data)
    }
}

func main() {
    processor := &Processor{
        onSuccess: func(data string) {
            fmt.Println("成功:", data)
        },
        onError: func(err error) {
            fmt.Println("エラー:", err)
        },
    }
    
    processor.Process("Hello")  // 成功: Hello
    processor.Process("")       // エラー: データが空です
}

8. 関数型の比較

nilとの比較

package main

import "fmt"

func main() {
    var f func()
    
    // nilチェック
    if f == nil {
        fmt.Println("関数はnilです")
    }
    
    f = func() {
        fmt.Println("実行されました")
    }
    
    if f != nil {
        f() // 実行されました
    }
}

関数同士の比較はできない

package main

func main() {
    f1 := func() {}
    f2 := func() {}
    
    // これはコンパイルエラー!
    // if f1 == f2 {
    //     // ...
    // }
    
    // nilとの比較だけ可能
    if f1 != nil {
        // OK
    }
}

まとめ: 関数型で覚えておくべきこと

関数型の基本

  1. 関数を値として扱える: 変数に代入、引数として渡す、戻り値として返す
  2. ゼロ値はnil: 初期化されていない関数型変数はnil
  3. シグネチャで定義: パラメータと戻り値の型で決まる

関数型の宣言

// 引数なし、戻り値なし
var f1 func()

// 引数あり、戻り値あり
var f2 func(int) int

// 複数の引数と戻り値
var f3 func(int, int) (int, int)

// 可変長引数
var f4 func(string, ...int) int

高階関数

// 関数を引数として受け取る
func apply(f func(int) int, x int) int {
    return f(x)
}

// 関数を返す
func makeAdder(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

クロージャ

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

実用的なアドバイス

package main

import "fmt"

func main() {
    // 1. nilチェックを忘れずに
    var f func()
    if f != nil {
        f()
    }
    
    // 2. 関数を変数に代入して再利用
    square := func(x int) int {
        return x * x
    }
    fmt.Println(square(5))
    
    // 3. 高階関数で汎用的な処理
    numbers := []int{1, 2, 3, 4, 5}
    evens := filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    
    // 4. クロージャで状態を保持
    counter := makeCounter()
    fmt.Println(counter())
    
    fmt.Println(evens)
}

func filter(nums []int, pred func(int) bool) []int {
    result := []int{}
    for _, n := range nums {
        if pred(n) {
            result = append(result, n)
        }
    }
    return result
}

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

関数型は、関数型プログラミングの基礎であり、柔軟で再利用可能なコードを書くための強力な機能です!

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました