1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. LearnTheBasics
  5. 関数(Functions)- Golang learning step 1-8

関数(Functions)- Golang learning step 1-8

  • 公開日
  • カテゴリ:LearnTheBasics
  • タグ:Golang,roadmap.sh,学習メモ
関数(Functions)- Golang learning step 1-8

roadmap.sh > Go > Learn the Basics > Functions の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 関数(Functions)
  3. 関数の基本構造
  4. Go 言語の関数名における命名規則
  5. 複数の戻り値
    1. エラーハンドリングの重要性
  6. 名前付き戻り値
  7. 可変長引数
  8. 無名関数
    1. 無名関数の具体的な使用例
  9. クロージャ
    1. クロージャの利点と使用場面

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

関数(Functions)

Go 言語における関数は、コードの再利用性と構造化を促進する基本的な構成要素です。

Go の関数は、入力を受け取り、処理を行い、結果を返す独立した処理単位であり、プログラムの他の部分から呼び出すことができる、再利用可能なコードの単位です。関数を使うことで、コードを整理し、モジュール化を進め、読みやすさを向上させることができます。

関数の基本構造

Go 言語の関数は、明確な構文で定義され、引数と戻り値の型を明示的に指定します。

以下のように定義します。

func 関数名(引数の型) 戻り値の型 {
  // 関数の処理
  return 戻り値
}

関数名の後に、引数と戻り値を指定します。関数の戻り値がない場合、戻り値の型は省略されます。Go では void というキーワードを使わず、単に戻り値の型を指定しないことで戻り値がないことを表現します。

例えば、以下のように関数を定義し、呼び出すことができます。

package main

import "fmt"

func greet(name string) string {
  return "Hello, " + name
}

func main() {
  // 関数の呼び出し
  message := greet("Alice")
  fmt.Println(message)
  // 出力: Hello, Alice

  // 関数の戻り値を直接使用
  fmt.Println(greet("Bob"))
  // 出力: Hello, Bob
}

この例では、greet 関数が name という文字列を受け取り、「Hello, [name]」という文字列を返します。main 関数内で greet 関数を 2 回呼び出しています。1 回目は戻り値を変数に格納してから使用し、2 回目は fmt.Println の引数として直接使用しています。

関数の引数や戻り値の型は、使用目的に応じて適切に設定することが重要です。例えば、数値を扱う関数なら int や float64 を、真偽値を扱う関数なら bool を使用します。

Go 言語の関数名における命名規則

  • 関数名は文字で始まり、その後に任意の数の文字や数字を含めることができます。
  • 関数名は数字で始めることはできません。
  • 関数名にスペースを含めることはできません。
  • 大文字で始まる関数名は、他のパッケージにエクスポートされます。小文字で始まる関数名は他のパッケージにエクスポートされませんが、同じパッケージ内で呼び出すことができます。
  • 関数名が複数の単語から成る場合、最初の単語以降の各単語の先頭を大文字にします。例:empName、EmpAddressなど。
  • 関数名は大文字と小文字を区別します(car、Car、CARは3つの異なる変数として扱われます)。

複数の戻り値

Go 言語の特徴的な機能の一つとして、関数が複数の値を同時に返すことができます。

以下の関数は、割り算の結果とエラーを返すパターンです。

package main

import "fmt"

func divide(a, b float64) (float64, error) {
  if b == 0 {
    return 0, fmt.Errorf("division by zero")
  }
  return a / b, nil
}

func main() {
  result, err := divide(1.0, 2.3)

  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println(result)
  // => 0.4347826086956522
}

エラーハンドリングの重要性

Go 言語では、エラーを明示的に処理することが推奨されています。上記の例のように、多くの関数が結果とともにエラーを返すのは、Go 言語の重要な設計思想の一つです。

  1. 明示的なエラーチェック: エラーを戻り値として返すことで、呼び出し側に明示的なエラーチェックを強制します。これにより、エラーが見落とされるリスクを減らし、プログラムの信頼性を向上させます。
  2. エラーの伝播: 下層の関数で発生したエラーを、必要に応じて上層の関数に伝播させることができます。これにより、適切なレベルでエラーを処理することが可能になります。
  3. カスタムエラー: fmt.Errorf や errors.New を使用して、状況に応じた詳細なエラーメッセージを作成できます。これにより、エラーの原因をより正確に特定し、デバッグを容易にします。
    • fmt.Errorf ではフォーマットを利用したメッセージ(string)を作成でき、errors.New はシンプルなエラーメッセージ(Errorオブジェクト)を作成します。

エラーを適切に処理することで、プログラムの堅牢性が向上し、予期せぬ動作を防ぐことができます。そのため、Go言語でプログラムを書く際は、常にエラーハンドリングを意識することが重要です。

名前付き戻り値

名前付き戻り値は、関数の内部で戻り値用の変数を初期化し、return ステートメントに値を指定せずにそのまま返すことができます。

package main

import "fmt"

func add(a, b int) (sum int) {
  sum = a + b
  // 明記していないが sum が return される
  return
}

func main() {
  result := add(1, 2)
  fmt.Println(result)
  // => 3
}

この場合、変数 sum が関数の戻り値として暗黙的に返されます。

名前付き戻り値を使用すると、戻り値用の変数を定義することで、長い関数や途中で複数の return ステートメントがある場合でも、簡単にコードを読みやすくできます。

可変長引数

Goでは、可変長引数(引数の数が可変)の関数を定義できます。可変長引数を使用することで、任意の数の引数を受け取る柔軟な関数を定義できます。

package main

import "fmt"

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

func main() {
  total := sum(1, 2, 3, 4, 5)
  fmt.Println(total)
  // => 15
}

無名関数

無名関数は、関数を値として扱い、柔軟なプログラミングスタイルを可能にします。関数を変数に代入したり、引数として渡すことができます。

package main

import "fmt"

func main() {
  add := func(a, b int) int {
  return a + b
  }
  result := add(3, 4)
  fmt.Println(result)
  // => 7
}

無名関数は、特にコールバックや一時的な関数を扱う際に便利です。例えば、イベントハンドリングや一時的な処理を行う場合に役立ちます。

また、無名関数は即座に実行される(Immediately-Invoked Function Expression, IIFE)」という利用パターンも Go では可能です。

package main

import "fmt"

func main() {
  result := func(a, b int) int {
    return a + b
  }(3, 4) // 無名関数を定義すると同時に実行

  fmt.Println(result)
  // => 7
}

無名関数の具体的な使用例

ソート関数のカスタマイズ

以下の例では、sort.Slice 関数に無名関数を渡して、スライスのソート順をカスタマイズしています。

package main

import (
  "fmt"
  "sort"
)

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

  sort.Slice(numbers, func(i, j int) bool {
    return numbers[i] > numbers[j]  // 降順にソート
  })

  fmt.Println(numbers)
  // 出力: [9 6 5 5 4 3 3 2 1 1]
}

遅延実行

以下の例では、goroutine 内で無名関数を使用して、メッセージの出力を遅延させています。

package main

import (
  "fmt"
  "time"
)

func main() {
  message := "Hello, World!"

  go func() {
    time.Sleep(2 * time.Second)
    fmt.Println(message)
  }()

  message = "Goodbye, World!"
  time.Sleep(3 * time.Second)
  // 2 秒後に "Goodbye, World!" が出力される
}

フィルタリング

以下の例では、filter 関数に無名関数を渡して、条件に合う要素のみをフィルタリングしています。

package main

import "fmt"

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

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

  evenNumbers := filter(numbers, func(n int) bool {
    return n%2 == 0
  })

  fmt.Println(evenNumbers)
  // 出力: [2 4 6 8 10]
}

これらの例は、無名関数がプログラムの柔軟性と表現力を高める方法を示しています。無名関数を使用することで、コードをより簡潔に、かつ読みやすくすることができます。

クロージャ

Go の関数はクロージャとして動作するため、関数内で定義された変数を保持できます。

package main

import "fmt"

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

func main() {
  // incrementer で生成された関数を取得
  inc := incrementer()

  // 何度か呼び出してみる
  fmt.Println(inc()) // => 1
  fmt.Println(inc()) // => 2
  fmt.Println(inc()) // => 3

  // もう一度 incrementer から別の関数を取得
  anotherInc := incrementer()

  // 新しい関数は別のクロージャとして動作する
  fmt.Println(anotherInc()) // => 1
  fmt.Println(anotherInc()) // => 2
}

incrementer は、count を保持し続ける関数を返します。

クロージャの利点と使用場面

クロージャには以下のような利点があり、様々な場面で活用できます。

  1. 状態の保持: クロージャは、その環境(レキシカルスコープ)内の変数を「覚えて」おくことができます。上記の例では、count 変数の状態が保持されています。これは、オブジェクト指向プログラミングにおけるプライベート変数のような役割を果たします。
  2. カプセル化: クロージャを使うことで、データ(変数)と、それを操作する関数をまとめることができます。これにより、グローバル変数の使用を減らし、コードの安全性を高めることができます。
  3. コールバック関数の作成: クロージャは、イベントハンドラやコールバック関数の作成に適しています。例えば、ボタンクリックのイベントハンドラなどに使用できます。
  4. 遅延実行(Lazy Evaluation): クロージャを使用することで、必要になるまで計算を遅らせることができます。これは、大量のデータを扱う際などに役立ちます。

使用場面の例:

package main

import "fmt"

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

func main() {
  // 10を足す関数を作成
  add10 := adder(10)
  
  // 20を足す関数を作成
  add20 := adder(20)
  
  fmt.Println(add10(5))  // => 15
  fmt.Println(add20(5))  // => 25
}

この例では、クロージャを使って特定の値を足す関数を動的に作成しています。これは、同じロジックを持つが少しずつ異なる複数の関数が必要な場合に特に有用です。

クロージャを適切に使用することで、コードの可読性、再利用性、保守性を向上させることができます。ただし、過度の使用は逆に複雑性を増す可能性があるため、適材適所で使用することが重要です。

まとめ

  • Go言語の関数は、コードの再利用性と構造化を促進する基本的な構成要素。
  • 関数の基本構造は明確で、引数と戻り値の型を明示的に指定する。
  • Go言語では複数の戻り値を返すことができ、これはエラーハンドリングに特に有用である。
  • エラーハンドリングは Go の重要な設計思想の一つで、明示的なエラーチェックを促進する。
  • 名前付き戻り値を使用することで、コードの可読性を向上させることができる。
  • 可変長引数を使用すると、任意の数の引数を受け取る柔軟な関数を定義できる。
  • 無名関数は、関数を値として扱うことを可能にし、コードの柔軟性を高める。
  • 無名関数は、ソート、遅延実行、フィルタリングなど、様々な場面で活用できる。
  • クロージャは、関数が終了した後も変数の状態を保持できる強力な機能である。
  • クロージャは状態の保持、カプセル化、コールバック関数の作成、遅延実行などに利用できる。


[Next] Step 1-9: 条件文(Conditional Statements)

[Prev] Step 1-7: パッケージ(Packages)

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級