
こんにちは。よっしーです(^^)
本日は、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
}
}
まとめ: 関数型で覚えておくべきこと
関数型の基本
- 関数を値として扱える: 変数に代入、引数として渡す、戻り値として返す
- ゼロ値はnil: 初期化されていない関数型変数は
nil - シグネチャで定義: パラメータと戻り値の型で決まる
関数型の宣言
// 引数なし、戻り値なし
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言語の言語仕様について解説しました。

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


コメント