[インデックス 1390] ファイルの概要
このコミットは、Go言語のtest/map.go
ファイルに対する変更です。具体的には、マップのテストコードが更新され、fmt
パッケージの出力形式がprintf
スタイルに統一され、マップのパフォーマンス向上に伴いテストの反復回数が増加し、構造体やマップをキーとする新しいテストケースが追加されています。
コミット
commit 62b06fa506f3403ae173116b7d4cdbb62d65db2f
Author: Rob Pike <r@golang.org>
Date: Sat Dec 20 16:07:43 2008 -0800
update map test.
convert to printf.
increase count to 1000 - maps are faster now.
add cases for structure values as key and maps.
R=rsc
DELTA=197 (66 added, 18 deleted, 113 changed)
OCL=21683
CL=21686
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/62b06fa506f3403ae173116b7d4cdbb62d65db2f
元コミット内容
update map test.
convert to printf.
increase count to 1000 - maps are faster now.
add cases for structure values as key and maps.
変更の背景
このコミットは、Go言語の初期開発段階におけるマップ(map
)の実装とテストの進化を反映しています。コミットメッセージから、以下の主要な背景が読み取れます。
- マップのパフォーマンス向上: コミットメッセージにある「maps are faster now」という記述は、Go言語のマップの実装が改善され、以前よりも効率的になったことを示唆しています。これに伴い、テストの反復回数を増やすことで、より広範なシナリオでのマップの堅牢性と性能を検証する必要が生じました。
fmt
パッケージの統一: 以前のテストコードでは、fmt.New()
でフォーマッタを生成し、F.s().d().putnl()
のようなメソッドチェーンで出力を行っていましたが、このコミットでC言語のprintf
に似たfmt.printf()
(現在のfmt.Printf()
に相当)への移行が行われています。これは、Go言語の標準ライブラリにおける出力APIの設計が固まりつつあった時期であり、より簡潔で一般的なフォーマット関数への統一が進められたと考えられます。- マップのキーとしての型のサポート拡張: 構造体(
struct
)や他のマップ(map
)をマップのキーとして使用するテストケースが追加されています。これは、Go言語のマップが、比較可能な任意の型をキーとして使用できるという設計原則を検証し、その機能が正しく動作することを確認するためのものです。特に、構造体をキーとする場合、その比較セマンティクス(フィールドごとの値の比較)が重要になります。
これらの変更は、Go言語のコアデータ構造であるマップの安定性、性能、および汎用性を向上させるための継続的な取り組みの一環として行われました。
前提知識の解説
Go言語のマップ (map)
Go言語のmap
は、キーと値のペアを格納する組み込みのデータ構造です。他の言語のハッシュマップ、ハッシュテーブル、辞書に相当します。
- 宣言と初期化:
make(map[KeyType]ValueType)
またはマップリテラルmap[KeyType]ValueType{key1: value1, key2: value2}
で作成します。 - キーの型: Goのマップのキーは、比較可能な型である必要があります。これは、
==
演算子で比較できる型を指します。具体的には、数値型、文字列型、ブール型、ポインタ型、チャネル型、インターフェース型、そして比較可能な要素を持つ配列や構造体が含まれます。関数、スライス、他のマップは比較可能ではないため、キーにはなれません。 - 値の型: 値の型は任意です。
- 要素のアクセス:
m[key]
で値にアクセスします。 - 要素の追加/更新:
m[key] = value
で追加または更新します。 - 要素の削除:
delete(m, key)
で削除します。 - 存在チェック:
value, ok := m[key]
の形式でアクセスすると、ok
というブール値でキーが存在するかどうかを確認できます。これは、値がゼロ値(例:int
なら0
、string
なら""
)である場合と、キーが存在しない場合を区別するために重要です。
fmt
パッケージと出力フォーマット
fmt
パッケージは、Go言語におけるフォーマットされたI/O(入出力)を提供します。C言語のprintf
やscanf
に似た機能を提供します。
fmt.Print
/fmt.Println
: 引数をデフォルトのフォーマットで出力します。Println
は最後に改行を追加します。fmt.Printf
: フォーマット指定子(%d
、%s
、%v
など)を使用して、指定されたフォーマットで文字列を出力します。%d
: 整数%s
: 文字列%g
: 浮動小数点数(適切な精度で表示)%t
: ブール値%v
: Goのデフォルトフォーマット(あらゆる型に対応)%#v
: Goの構文で値を表示%T
: 値の型を表示
fmt.New()
と旧来の出力: このコミット以前のGoの初期バージョンでは、fmt.New()
のようなメソッドが存在し、F.s().d().putnl()
のようにメソッドチェーンで出力を構築するスタイルが使われていたようです。しかし、これは標準的なfmt.Printf
のスタイルとは異なり、現在のGo言語ではfmt.New()
は標準のfmt
パッケージには存在しません。このコミットは、より一般的なprintf
スタイルのAPIへの移行を示しています。
構造体の比較
Go言語において、構造体は以下の条件を満たす場合に比較可能です。
- すべてのフィールドが比較可能であること。
- 構造体自体が比較可能であること。
比較可能な構造体は、
==
演算子や!=
演算子で比較できます。比較はフィールドごとに再帰的に行われます。
技術的詳細
このコミットの技術的詳細は、主にtest/map.go
ファイル内の変更に集約されます。
-
fmt
パッケージの利用方法の変更:- 以前は
import fmt "fmt"
としてfmt.New()
でフォーマッタを作成し、F.s().d().putnl()
のようなメソッドチェーンで出力を行っていました。 - 変更後は、
import ("fmt"; "strconv";)
となり、fmt.printf()
(現在のfmt.Printf()
)が直接使用されています。これにより、出力コードがより簡潔で標準的なGoのスタイルに統一されました。 - 例:
F.s("len(mib) = ").d(len(mib)).putnl();
がfmt.printf("len(mib) = %d\\n", len(mib));
に変更されています。
- 以前は
-
テスト反復回数の増加:
const count = 100;
がconst count = 1000;
に変更されました。これは、コミットメッセージにある「maps are faster now」という背景を裏付けるもので、マップの性能が向上したため、より多くの要素を扱うテストが可能になったことを示しています。これにより、マップの実装の堅牢性とスケーラビリティがより厳密に検証されます。
-
strconv
パッケージの導入:- 整数を文字列に変換するために、以前は
F.d(i).str()
のようなfmt
パッケージの内部的な変換メソッドが使われていましたが、このコミットでstrconv.itoa(i)
が導入されました。strconv
パッケージは、文字列と数値の変換に特化した標準パッケージであり、より適切なAPIの利用に移行したことを示します。
- 整数を文字列に変換するために、以前は
-
マップリテラルのテスト追加:
mlit := map[string] int { "0":0, "1":1, "2":2, "3":3, "4":4 };
のように、マップリテラルを直接初期化してテストするコードが追加されました。これは、Go言語のマップリテラル構文の動作確認と、その初期化が正しく行われることを検証するためのものです。
-
構造体をキーとするマップのテスト追加:
type T struct { i int64; f float; };
という構造体が定義され、mti := new(map[T] int);
(構造体をキーとするマップ)とmit := new(map[int] T);
(構造体を値とするマップ)が追加されました。- 以前は
//mit := new(map[int] T); // should be able to do a value but: fatal error: algtype: cant find type <T>{}
のようにコメントアウトされており、構造体を値とするマップでコンパイルエラーが発生していたことが示唆されています。このコミットでそれが修正され、構造体をキーまたは値とするマップが正しく動作することがテストされるようになりました。 T
構造体のフィールドがs string
からi int64
に変更されています。これは、構造体をマップのキーとして使用する際に、文字列フィールドの比較が複雑であったり、初期の実装で問題があった可能性を示唆しています。int64
に変更することで、より単純な比較が可能になり、テストの焦点をマップのキーとしての構造体の動作に絞ったと考えられます。
-
マップを値とするマップのテスト追加:
type M map[int] int;
というマップ型が定義され、mipM := new(map[int] M);
(マップを値とするマップ)が追加されました。これにより、ネストされたマップの動作がテストされるようになりました。
-
マップ要素の更新テストの追加:
mspa[s][i % 2] = "deleted";
のように、マップの値が配列である場合にその要素を更新するテストが追加されました。mipT[i].i += 1;
やmipT[i].f = float(i + 1);
のように、マップの値が構造体である場合にそのフィールドを更新するテストが追加されました。mipM[i][i]++;
のように、マップの値が別のマップである場合にそのネストされたマップの要素を更新するテストが追加されました。 これらのテストは、マップに格納された複雑なデータ構造が正しく更新されることを検証します。
全体として、このコミットはGo言語のマップ実装の成熟度を高め、より多様なユースケースに対応できるようテストカバレッジを拡大したことを示しています。
コアとなるコードの変更箇所
test/map.go
ファイル全体が大幅に変更されていますが、特に以下の点がコアとなる変更箇所です。
-
import
文の変更とfmt
の利用方法の変更:--- a/test/map.go +++ b/test/map.go @@ -6,7 +6,10 @@ package main -import fmt "fmt" +import ( + "fmt"; + "strconv"; +) const arraylen = 2; // BUG: shouldn't need this
そして、ファイル全体にわたる
F.s().d().putnl()
形式からfmt.printf()
形式への変更。 -
マップリテラルのテスト追加:
--- a/test/map.go +++ b/test/map.go @@ -23,9 +26,14 @@ func P(a []string) string { } func main() { - F := fmt.New(); - - // BUG: should test a map literal when there's syntax + // Test a map literal. + mlit := map[string] int { "0":0, "1":1, "2":2, "3":3, "4":4 }; + for i := 0; i < len(mlit); i++ { + s := string([]byte{byte(i)+'0'}); + if mlit[s] != i { + fmt.printf("mlit[%s] = %d\\n", s, mlit[s]) + } + } mib := new(map[int] bool); mii := new(map[int] int);
-
T
構造体の変更と構造体をキー/値とするマップのテスト追加:--- a/test/map.go +++ b/test/map.go @@ -38,254 +46,277 @@ func main() { // BUG need an interface map both ways too type T struct { - s string; + i int64; // can't use string here; struct values are only compared at the top level f float; }; mipT := new(map[int] *T); mpTi := new(map[*T] int); - //mit := new(map[int] T); // should be able to do a value but: fatal error: algtype: cant find type <T>{}\n- //mti := new(map[T] int); // should be able to do a value but: fatal error: algtype: cant find type <T>{}\n+ mit := new(map[int] T); + mti := new(map[T] int); type M map[int] int; mipM := new(map[int] M); - const count = 100; // BUG: should be bigger but maps do linear lookup + const count = 1000;
そして、
for
ループ内でのmit
とmti
への値の代入と検証。 -
count
定数の変更:--- a/test/map.go +++ b/test/map.go @@ -38,254 +46,277 @@ func main() { // BUG need an interface map both ways too type T struct { - s string; + i int64; // can't use string here; struct values are only compared at the top level f float; }; mipT := new(map[int] *T); mpTi := new(map[*T] int); - //mit := new(map[int] T); // should be able to do a value but: fatal error: algtype: cant find type <T>{}\n- //mti := new(map[T] int); // should be able to do a value but: fatal error: algtype: cant find type <T>{}\n+ mit := new(map[int] T); + mti := new(map[T] int); type M map[int] int; mipM := new(map[int] M); - const count = 100; // BUG: should be bigger but maps do linear lookup + const count = 1000;
-
マップ要素の更新テストの追加:
--- a/test/map.go +++ b/test/map.go @@ -415,23 +462,24 @@ func main() { // tests for structured map element updates for i := 0; i < count; i++ {\n- s := F.d(i).str();\n+ s := strconv.itoa(i);\n mspa[s][i % 2] = "deleted"; if mspa[s][i % 2] != "deleted" { - F.s("mspa[").d(i).s("][").d(i).s("%2] =").s(mspa[s][i % 2]).putnl(); + fmt.printf("update mspa[%s][%d] = %s\\n", s, i %2, mspa[s][i % 2]); } - mipT[i].s = string('a' + i % 26) + mipT[i].s[1:len(s)]; - first := string('a' + i % 26); - if mipT[i].s != first + s[1:len(s)] { - F.s("mit[").d(i).s("].s = ").s(mipT[i].s).putnl(); +\n+ mipT[i].i += 1; + if mipT[i].i != int64(i)+1 { + fmt.printf("update mipT[%d].i = %d\\n", i, mipT[i].i);\n } mipT[i].f = float(i + 1); if (mipT[i].f != float(i + 1)) { - F.s("mipT[").d(i).s("].f = ").g(mipT[i].f).putnl(); + fmt.printf("update mipT[%d].f = %g\\n", i, mipT[i].f);\n } +\n mipM[i][i]++; if mipM[i][i] != (i + 1) + 1 { - F.s("mipM[").d(i).s("][").d(i).s("] = ").d(mipM[i][i]).putnl(); + fmt.printf("update mipM[%d][%d] = %i\\n", i, i, mipM[i][i]); } } }
コアとなるコードの解説
fmt
パッケージの利用方法の変更
Go言語の初期段階では、fmt
パッケージのAPIがまだ安定していなかったことが伺えます。fmt.New()
でフォーマッタオブジェクトを作成し、そのメソッドをチェーンして出力するスタイルは、現在のGoでは見られません。このコミットは、よりC言語のprintf
に似たfmt.printf()
(現在のfmt.Printf()
)への移行を示しています。これは、Goの標準ライブラリがより一貫性のある、使いやすいAPIデザインへと進化していく過程の一部です。printf
スタイルは、フォーマット文字列と可変引数リストを使用するため、より柔軟で表現力豊かな出力が可能です。
マップリテラルのテスト追加
mlit := map[string] int { "0":0, "1":1, "2":2, "3":3, "4":4 };
の追加は、Go言語のマップリテラル構文が導入され、その機能が正しく動作することを検証するためのものです。マップリテラルは、コード内で直接マップを初期化する簡潔な方法を提供し、可読性を向上させます。このテストは、リテラルで初期化されたマップが期待通りのキーと値を保持していることを確認します。
T
構造体の変更と構造体をキー/値とするマップのテスト追加
type T struct { i int64; f float; };
の変更と、mti := new(map[T] int);
および mit := new(map[int] T);
のテスト追加は、Goのマップが構造体をキーまたは値として扱う能力を検証するものです。
T
構造体の変更: 以前のs string
からi int64
への変更は重要です。Goのマップのキーは比較可能である必要があります。文字列は比較可能ですが、初期の実装では構造体内の文字列フィールドの比較に問題があったか、あるいはより単純な数値型でテストの焦点を絞りたかった可能性があります。int64
は単純な値比較が可能であり、構造体をキーとするマップの基本的な動作を検証するのに適しています。- 構造体をキーとするマップ (
mti
): 構造体をマップのキーとして使用する場合、Goは構造体のすべてのフィールドが比較可能であれば、それらをフィールドごとに比較してキーの等価性を判断します。このテストは、構造体の値が正しくハッシュされ、マップ内で一意のキーとして機能することを確認します。 - 構造体を値とするマップ (
mit
): 構造体をマップの値として使用することは一般的であり、このテストはマップが構造体の値を正しく格納し、取得できることを確認します。以前のコメントアウトされた行は、初期の実装で構造体を値とするマップに問題があったことを示唆しており、このコミットでその問題が解決されたことを意味します。
count
定数の変更
const count = 100;
から const count = 1000;
への変更は、Goのマップ実装の性能が向上したことを直接的に示しています。テストの反復回数を増やすことで、より大規模なデータセットでのマップの挿入、検索、削除の効率と安定性を検証できます。これは、Goのマップが実用的なアプリケーションで高いパフォーマンスを発揮するための重要なステップです。
マップ要素の更新テストの追加
これらのテストは、マップに格納されている複雑なデータ構造(配列、構造体、他のマップ)が、マップを介して正しく変更できることを確認します。Goのマップは値のコピーを格納するのではなく、値への参照(ポインタではないが、値そのもの)を格納するため、マップから取得した構造体や配列、マップの要素を直接変更すると、元のマップ内の値も変更されます。これらのテストは、この挙動が期待通りに機能することを検証し、Goのマップが単なるキーと値のペアのコンテナではなく、より動的なデータ構造を扱うことができることを示しています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Go言語のマップに関するドキュメント: https://go.dev/blog/maps
fmt
パッケージのドキュメント: https://pkg.go.dev/fmtstrconv
パッケージのドキュメント: https://pkg.go.dev/strconv
参考にした情報源リンク
- Web search results for "Go fmt.New() vs fmt.Printf early versions":
- Based on the available information, there is no standard function named
New()
within Go'sfmt
package, neither in current versions nor in historical documentation. Thefmt
package primarily provides functions for formatted I/O, analogous to C'sprintf
andscanf
functions. - The core functions for printing and formatting in the
fmt
package have consistently included:fmt.Print()
,fmt.Println()
,fmt.Printf()
,fmt.Sprintf()
,fmt.Fprint()
,fmt.Fprintf()
,fmt.Fprintln()
,fmt.Errorf()
. - Therefore, a direct comparison between
fmt.New()
andfmt.Printf
in early Go versions is not applicable, asfmt.New()
does not appear to be a part of the standardfmt
package's public API.fmt.Printf
has always been the go-to function for formatted output. - Sources: go.dev, dev.to, medium.com, yourbasic.org, stackoverflow.com, gobyexample.com, reddit.com, linuxcommandlibrary.com.
- Based on the available information, there is no standard function named