Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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)の実装とテストの進化を反映しています。コミットメッセージから、以下の主要な背景が読み取れます。

  1. マップのパフォーマンス向上: コミットメッセージにある「maps are faster now」という記述は、Go言語のマップの実装が改善され、以前よりも効率的になったことを示唆しています。これに伴い、テストの反復回数を増やすことで、より広範なシナリオでのマップの堅牢性と性能を検証する必要が生じました。
  2. fmtパッケージの統一: 以前のテストコードでは、fmt.New()でフォーマッタを生成し、F.s().d().putnl()のようなメソッドチェーンで出力を行っていましたが、このコミットでC言語のprintfに似たfmt.printf()(現在のfmt.Printf()に相当)への移行が行われています。これは、Go言語の標準ライブラリにおける出力APIの設計が固まりつつあった時期であり、より簡潔で一般的なフォーマット関数への統一が進められたと考えられます。
  3. マップのキーとしての型のサポート拡張: 構造体(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なら0stringなら"")である場合と、キーが存在しない場合を区別するために重要です。

fmtパッケージと出力フォーマット

fmtパッケージは、Go言語におけるフォーマットされたI/O(入出力)を提供します。C言語のprintfscanfに似た機能を提供します。

  • 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ファイル内の変更に集約されます。

  1. 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)); に変更されています。
  2. テスト反復回数の増加:

    • const count = 100;const count = 1000; に変更されました。これは、コミットメッセージにある「maps are faster now」という背景を裏付けるもので、マップの性能が向上したため、より多くの要素を扱うテストが可能になったことを示しています。これにより、マップの実装の堅牢性とスケーラビリティがより厳密に検証されます。
  3. strconvパッケージの導入:

    • 整数を文字列に変換するために、以前はF.d(i).str()のようなfmtパッケージの内部的な変換メソッドが使われていましたが、このコミットでstrconv.itoa(i)が導入されました。strconvパッケージは、文字列と数値の変換に特化した標準パッケージであり、より適切なAPIの利用に移行したことを示します。
  4. マップリテラルのテスト追加:

    • mlit := map[string] int { "0":0, "1":1, "2":2, "3":3, "4":4 }; のように、マップリテラルを直接初期化してテストするコードが追加されました。これは、Go言語のマップリテラル構文の動作確認と、その初期化が正しく行われることを検証するためのものです。
  5. 構造体をキーとするマップのテスト追加:

    • 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に変更することで、より単純な比較が可能になり、テストの焦点をマップのキーとしての構造体の動作に絞ったと考えられます。
  6. マップを値とするマップのテスト追加:

    • type M map[int] int; というマップ型が定義され、mipM := new(map[int] M);(マップを値とするマップ)が追加されました。これにより、ネストされたマップの動作がテストされるようになりました。
  7. マップ要素の更新テストの追加:

    • mspa[s][i % 2] = "deleted"; のように、マップの値が配列である場合にその要素を更新するテストが追加されました。
    • mipT[i].i += 1;mipT[i].f = float(i + 1); のように、マップの値が構造体である場合にそのフィールドを更新するテストが追加されました。
    • mipM[i][i]++; のように、マップの値が別のマップである場合にそのネストされたマップの要素を更新するテストが追加されました。 これらのテストは、マップに格納された複雑なデータ構造が正しく更新されることを検証します。

全体として、このコミットはGo言語のマップ実装の成熟度を高め、より多様なユースケースに対応できるようテストカバレッジを拡大したことを示しています。

コアとなるコードの変更箇所

test/map.goファイル全体が大幅に変更されていますが、特に以下の点がコアとなる変更箇所です。

  1. 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()形式への変更。

  2. マップリテラルのテスト追加:

    --- 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);
    
  3. 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ループ内でのmitmtiへの値の代入と検証。

  4. 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;
    
  5. マップ要素の更新テストの追加:

    --- 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のマップが単なるキーと値のペアのコンテナではなく、より動的なデータ構造を扱うことができることを示しています。

関連リンク

参考にした情報源リンク

  • 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's fmt package, neither in current versions nor in historical documentation. The fmt package primarily provides functions for formatted I/O, analogous to C's printf and scanf 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() and fmt.Printf in early Go versions is not applicable, as fmt.New() does not appear to be a part of the standard fmt 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.