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

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

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

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

スポンサーリンク

背景

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

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

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

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

パッケージの例

以下は、並行処理による素数のふるい(エラトステネスのふるい)を実装した、完全なGoパッケージです。

package main

import "fmt"

// 2, 3, 4, … という数列をチャネル 'ch' に送る。
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i  // 'i' をチャネル 'ch' に送る。
	}
}

// チャネル 'src' の値をチャネル 'dst' にコピーする。
// ただし 'prime' で割り切れる値は取り除く。
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {  // 'src' から受け取った値をループで処理する。
		if i%prime != 0 {
			dst <- i  // 'i' をチャネル 'dst' に送る。
		}
	}
}

// 素数のふるい:filter プロセスを数珠つなぎにする。
func sieve() {
	ch := make(chan int)  // 新しいチャネルを作る。
	go generate(ch)       // generate() をサブプロセスとして起動する。
	for {
		prime := <-ch
		fmt.Print(prime, "\n")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}

func main() {
	sieve()
}

解説

結論:このコードは、Goの目玉機能である「ゴルーチン(軽量な並行処理)」と「チャネル(処理間でデータをやり取りする管)」を使って、素数を次々と求めるプログラム。フィルターを数珠つなぎにして、合成数をふるい落としていく。

初心者にとってこのプログラムはかなり高度です。ここでは「何をやっているのか」のイメージをつかむことを目標に、登場する道具を順に説明します。

まず登場する2つの新しい道具

ゴルーチン(goroutine)go 関数() と書くと、その関数を別の流れで同時に実行できます。原文のコメントでは「サブプロセス」と表現されていますが、実体はOSのプロセスより遥かに軽い「軽量スレッド」です。料理に例えると、メインの作業をしながら、別のコンロでも同時に煮込みを進めるイメージです。

チャネル(channel):ゴルーチン同士が**データを受け渡しするための管(パイプ)**です。make(chan int) で「int を流す管」を作り、ch <- i で送り込み、<-ch で取り出します。ベルトコンベアに荷物を載せる・受け取る、とイメージしてください。

ch := make(chan int) // int 型の管を作る
ch <- 5              // 管に 5 を送り込む(送信)
x := <-ch            // 管から取り出す(受信)

矢印付きの型(送信専用・受信専用)

関数の引数にある chan<- int<-chan int は、チャネルの「向き」を制限する書き方です。

書き方意味
chan<- int送信専用(このチャネルには送り込むだけ)
<-chan int受信専用(このチャネルからは取り出すだけ)
chan int送受信どちらもOK

矢印 <- がチャネル(chan)のどちら側にあるかで向きが決まります。これは「この関数はこのチャネルを送る専門/受け取る専門ですよ」と明示し、誤った使い方をコンパイル時に防ぐための仕組みです。

3つの関数の役割

generate:数を無限に作り続ける

func generate(ch chan<- int) {
	for i := 2; ; i++ {  // i を 2 から無限に増やし続ける
		ch <- i
	}
}

for i := 2; ; i++ は条件部分が空なので無限ループです。2, 3, 4, 5… と整数を延々とチャネルに送り続けます。「素数の候補」をすべて生み出す係です。

filter:特定の数で割り切れるものを捨てる

func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {
		if i%prime != 0 {  // prime で割り切れない数だけ
			dst <- i        // 次へ送る
		}
	}
}

src から流れてくる数のうち、prime で割り切れる数(=合成数)を捨て、割り切れない数だけを dst へ流します。% は割り算の余りを求める演算子で、i%prime != 0 は「prime で割り切れない」を意味します。例えば prime が 2 なら、偶数を全部ふるい落とす「2のフィルター」になります。

sieve:フィルターを数珠つなぎにする本体

ここがこのプログラムの核心です。動きを順に追います。

func sieve() {
	ch := make(chan int)
	go generate(ch)       // 2,3,4,5,… を流し始める
	for {
		prime := <-ch         // 管の先頭から1つ取り出す → これが素数
		fmt.Print(prime, "\n") // 素数を表示
		ch1 := make(chan int)
		go filter(ch, ch1, prime) // 「primeのフィルター」を新設して連結
		ch = ch1              // 次回はフィルター通過後の管を見る
	}
}

仕組みを段階で説明します。

最初、generate が 2, 3, 4, 5, 6, 7… を流します。sieve は管の先頭の 2 を取り出します。最初に出てくる数は必ず素数なので、2 を表示します。そして「2で割り切れる数を捨てるフィルター」を管の先に追加します。

すると、フィルター通過後の管には 3, 5, 7, 9, 11…(奇数)が流れます。次に先頭の 3 を取り出して表示し、「3で割り切れる数を捨てるフィルター」をさらに追加します。

次は 5, 7, 11, 13…(2でも3でも割れない数)が流れ、先頭の 5 が素数として出てきて、「5のフィルター」を追加…という具合に、素数が見つかるたびにその数のフィルターを管の末尾に継ぎ足していきます

最終的に、こんなパイプラインが動的に伸びていきます。

generate → [2を除外] → [3を除外] → [5を除外] → [7を除外] → … → sieve
   2,3,4,5,…   3,5,7,…    5,7,11,…    7,11,13,…

水道管に例えると、最初の管に整数を全部流し込み、「2の倍数を漉し取る網」「3の倍数を漉し取る網」…と網を次々に追加していくイメージです。すべての網をくぐり抜けて最初に出てきた数は、それまでのどの素数でも割り切れない=新しい素数だ、というわけです。これがエラトステネスのふるいを並行処理で表現したものです。

main 関数

func main() {
	sieve()
}

main はプログラムの開始地点です。sieve() を呼ぶだけで、上記の無限の素数生成が始まり、2, 3, 5, 7, 11, 13… と延々と表示され続けます(無限ループなので、止めるまで終わりません)。


補足1:原文のコメントが generate を「サブプロセス(subprocess)」と呼んでいますが、これは概念をわかりやすく言ったもので、技術的にはOSのプロセスではなくゴルーチンです。混同しないよう注意してください。

補足2:このプログラムは「Goの並行処理の表現力」を見せるための教育的な例であり、素数を高速に大量計算する用途には向きません(素数が増えるほどフィルターの鎖が長くなり非効率です)。仕組みの美しさを味わうサンプルだと捉えてください。

おわりに 

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

よっしー
よっしー

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

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

コメント

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