[インデックス 1867] ファイルの概要
このコミットは、Go言語の初期段階において、container/vector パッケージにイテレータ機能を追加したものです。具体的には、for ... range ループで使用できるチャネルベースのイテレータを導入し、vector の要素を効率的に走査できるようにしました。
コミット
commit 8d44052b6dd9e27a5230f66239681cec601d3a8e
Author: Rob Pike <r@golang.org>
Date: Mon Mar 23 17:46:59 2009 -0700
iterator for vector
R=rsc
DELTA=35 (35 added, 0 deleted, 0 changed)
OCL=26662
CL=26662
---
src/lib/container/vector.go | 16 ++++++++++++++++
src/lib/container/vector_test.go | 19 +++++++++++++++++++
2 files changed, 35 insertions(+)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8d44052b6dd9e27a5230f66239681cec601d3a8e
元コミット内容
iterator for vector
R=rsc
DELTA=35 (35 added, 0 deleted, 0 changed)
OCL=26662
CL=26662
変更の背景
このコミットは、Go言語がまだ活発に開発されていた初期の段階で行われました。当時のGoには、現在のような組み込みのイテレータプロトコルやジェネリクスは存在しませんでした。しかし、コレクション(この場合は vector)の要素を効率的かつ慣用的に走査するメカニズムは不可欠でした。
Go言語の設計思想の一つに「並行性 (concurrency) は言語の中心的な要素であるべき」というものがあります。この思想に基づき、Goはゴルーチン(軽量スレッド)とチャネル(ゴルーチン間の通信手段)を言語のプリミティブとして提供しています。このコミットは、このチャネルの特性を活かして、コレクションのイテレーションを実現しようとした初期の試みの一つです。
for ... range ループは、Go言語の強力な機能の一つであり、スライス、配列、マップ、文字列、そしてチャネルといった様々なデータ構造を簡潔に反復処理するために使用されます。このコミットの目的は、vector 型もこの for ... range ループの恩恵を受けられるようにすることでした。チャネルを介して要素をストリームとして提供することで、vector の内部実装を隠蔽しつつ、統一されたイテレーションインターフェースを提供することが可能になります。
前提知識の解説
Go言語のチャネル (Channels)
Go言語のチャネルは、ゴルーチン間で値を送受信するためのパイプのようなものです。チャネルは、並行処理における同期と通信の主要なメカニズムとして機能します。
- 作成:
c := make(chan Type)で作成します。Typeはチャネルが送受信するデータの型です。 - 送信:
c <- valueでチャネルに値を送信します。 - 受信:
value := <-cでチャネルから値を受信します。 - クローズ:
close(c)でチャネルをクローズします。クローズされたチャネルからの受信は、チャネルにまだ値が残っていればその値を返し、値がなくなればゼロ値を返します。クローズ後にチャネルに送信しようとするとパニックが発生します。 for ... rangeとチャネル:for v := range cのようにチャネルをfor ... rangeループで使用すると、チャネルがクローズされるまでチャネルから値を受信し続けます。チャネルがクローズされるとループは終了します。
ゴルーチン (Goroutines)
ゴルーチンは、Goランタイムによって管理される軽量な実行スレッドです。数千、数万のゴルーチンを同時に実行してもオーバーヘッドが非常に小さいのが特徴です。go キーワードの後に任意の関数呼び出しを記述することで、新しいゴルーチンを起動できます。
container/vector パッケージ (初期のGo)
Go言語の初期には、標準ライブラリに container パッケージが存在し、その中に vector 型が含まれていました。これは、動的な配列(現在のスライスに近い概念)を提供するものでした。現在のGoでは、組み込みのスライス型が非常に強力であるため、container/vector パッケージはほとんど使われなくなっています。しかし、このコミットが行われた当時は、vector がコレクションの基本的な実装の一つでした。
技術的詳細
このコミットは、vector 型に iterate と Iter という2つのメソッドを追加することで、チャネルベースのイテレータを実装しています。
-
func (p *Vector) iterate(c chan Element):- このメソッドは、
vectorの内部配列p.aをループで走査します。 - 各要素を引数として受け取ったチャネル
cにc <- p.a[i]の形式で送信します。 - 全ての要素を送信し終えた後、
close(c)を呼び出してチャネルをクローズします。これは、for ... rangeループがチャネルの終了を検知するために不可欠です。 - このメソッドは、通常、別のゴルーチンで実行されることを意図しています。
- このメソッドは、
-
func (p *Vector) Iter() chan Element:- このメソッドは、イテレータの「ファクトリ」として機能します。
- まず、
c := make(chan Element)を使って新しいチャネルを作成します。このチャネルが、イテレーション中に要素をストリームとして提供するために使用されます。 - 次に、
go p.iterate(c)を呼び出して、iterateメソッドを新しいゴルーチンで起動します。これにより、iterateメソッドがバックグラウンドで要素をチャネルに送信し続けることができます。 - 最後に、作成したチャネル
cを返します。
この設計により、ユーザーは vector オブジェクトに対して Iter() メソッドを呼び出すことでチャネルを取得し、そのチャネルを for ... range ループで直接使用できるようになります。
// 例: vectorの要素をイテレートする
myVector := vector.NewIntVector(5)
myVector.Set(0, 10)
myVector.Set(1, 20)
// ...
for v := range myVector.Iter() {
// v は vector の各要素
fmt.Println(v)
}
このパターンは、Go言語における「ジェネレータ」または「コルーチン」のような動作を実現する一般的な方法です。チャネルがバッファリングされていない場合、iterate ゴルーチンは Iter() を呼び出したゴルーチンがチャネルから値を受信するまでブロックされます。これにより、要素がオンデマンドで生成されるかのように振る舞い、メモリ効率の良いイテレーションが可能になります。
コアとなるコードの変更箇所
src/lib/container/vector.go
// Iterate over all elements; driver for range
func (p *Vector) iterate(c chan Element) {
for i := 0; i < len(p.a); i++ {
c <- p.a[i]
}
close(c);
}
// Channel iterator for range.
func (p *Vector) Iter() chan Element {
c := make(chan Element);
go p.iterate(c);
return c;
}
src/lib/container/vector_test.go
func TestIter(t *testing.T) {
const Len = 100;
x := vector.New(Len);
for i := 0; i < Len; i++ {
x.Set(i, i*i);
}
i := 0;
for v := range x.Iter() {
if v.(int) != i*i {
t.Error("Iter expected", i*i, "got", v.(int))
}
i++;
}
if i != Len {
t.Error("Iter stopped at", i, "not", Len)
}
}
コアとなるコードの解説
src/lib/container/vector.go の変更
-
iterateメソッド:c chan Elementを引数に取り、Element型の値を送信するためのチャネルを受け取ります。for i := 0; i < len(p.a); i++ループでvectorの内部スライスp.aの全要素を走査します。c <- p.a[i]で現在の要素をチャネルcに送信します。- ループが終了した後、
close(c)を呼び出してチャネルをクローズします。これは、Iter()メソッドから返されたチャネルをfor ... rangeループで消費する際に、ループがいつ終了するかを通知するために非常に重要です。チャネルがクローズされないと、for ... rangeループは永遠にブロックされる可能性があります。
-
Iterメソッド:chan Elementを戻り値として、要素をストリームとして提供するチャネルを返します。c := make(chan Element)で新しいチャネルを作成します。このチャネルは、iterateゴルーチンとIterを呼び出したゴルーチン間の通信に使用されます。go p.iterate(c)は、iterateメソッドを新しいゴルーチンとして起動します。これにより、iterateは非同期に要素をチャネルにプッシュし、Iterを呼び出した側はチャネルから要素をプルできるようになります。return cで、作成したチャネルを呼び出し元に返します。このチャネルがfor ... rangeループの対象となります。
src/lib/container/vector_test.go の変更
TestIter関数:const Len = 100でテスト用のベクターの長さを定義します。x := vector.New(Len)で新しいvectorインスタンスを作成します。for i := 0; i < Len; i++ { x.Set(i, i*i); }で、ベクターにi*iの値を設定し、テストデータを準備します。for v := range x.Iter() { ... }がこのテストの核心部分です。x.Iter()が返すチャネルをfor ... rangeループで消費し、ベクターの要素をイテレートします。if v.(int) != i*i { t.Error(...) }で、イテレートされた値が期待通りであるか(i*iと一致するか)をアサートします。v.(int)は型アサーションで、Elementインターフェースから具体的なint型に変換しています。i++でイテレーションの回数をカウントします。- ループ終了後、
if i != Len { t.Error(...) }で、全ての要素がイテレートされたか(iがLenと一致するか)を確認します。
このテストは、Iter メソッドが正しくチャネルを介して vector の全要素を提供し、for ... range ループが期待通りに動作することを確認しています。
関連リンク
- Go言語の公式ドキュメント (現在のチャネルに関する情報): https://go.dev/tour/concurrency/2
- Go言語の
for ... rangeステートメントに関するドキュメント: https://go.dev/tour/moretypes/16
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/8d44052b6dd9e27a5230f66239681cec601d3a8e
- Go言語の初期の設計に関する議論やドキュメント (一般的な知識として)