[インデックス 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言語の初期の設計に関する議論やドキュメント (一般的な知識として)