[インデックス 12742] ファイルの概要
このコミットは、Go言語の公式ドキュメントである effective_go.html
と、その中で使用されているサンプルコード eff_bytesize.go
に修正と改善を加えるものです。主な目的は、ドキュメントの記述をより正確にし、特にカスタム型における String()
メソッドと fmt
パッケージの相互作用に関する説明を明確にすることにあります。
コミット
- コミットハッシュ:
4074795e151813f303d5500d255901c6a3a796ef
- 作者: Rob Pike r@golang.org
- コミット日時: 2012年3月25日 (日) 11:34:51 +1100
- コミットメッセージ:
effective_go: cleanups and fixes Also explain the situation with recursive String methods more accurately, and clean up the code now that the fmt package is more careful. R=golang-dev, minux.ma, bradfitz CC=golang-dev https://golang.org/cl/5907047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4074795e151813f303d5500d255901c6a3a796ef
元コミット内容
effective_go: cleanups and fixes
Also explain the situation with recursive String methods more accurately,
and clean up the code now that the fmt package is more careful.
R=golang-dev, minux.ma, bradfitz
CC=golang-dev
https://golang.org/cl/5907047
変更の背景
このコミットの背景には、Go言語の標準ライブラリである fmt
パッケージの進化と、それによって可能になった String()
メソッドの実装における簡素化があります。
effective_go
は、Go言語のイディオムやベストプラクティスを解説する公式ドキュメントであり、Goプログラマーにとって非常に重要なリソースです。このドキュメントは、Go言語の設計思想や効率的なコーディングスタイルを理解するために広く参照されています。
以前の fmt
パッケージの挙動では、カスタム型が String()
メソッドを実装し、その String()
メソッド内で fmt.Sprintf
を呼び出す際に、無限再帰に陥る可能性がありました。特に、%s
や %v
のような文字列フォーマット指定子を使用した場合、fmt.Sprintf
は引数の型が fmt.Stringer
インターフェースを実装しているかをチェックし、もし実装していればその String()
メソッドを呼び出そうとします。これにより、String()
メソッドが自身を再帰的に呼び出し、スタックオーバーフローを引き起こす可能性がありました。
この問題を回避するため、以前は ByteSize
型の String()
メソッドのように、fmt.Sprintf
に渡す前に明示的に float64
に型変換を行うなどの工夫が必要でした。しかし、fmt
パッケージが「より注意深く (more careful)」なった、つまり内部的な挙動が改善されたことで、このような明示的な型変換が不要になり、より直感的で簡潔なコードが書けるようになりました。
このコミットは、この fmt
パッケージの改善を反映し、effective_go
ドキュメント内の説明とサンプルコードを更新することで、読者が最新かつ正確な情報に基づいたGoプログラミングを学べるようにすることを目的としています。また、range
ループや new
キーワード、ブランク識別子に関する説明も、より明確で正確なものに修正されています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念を理解しておく必要があります。
-
effective_go
ドキュメント: Go言語の公式ドキュメントの一つで、Go言語のイディオム、ベストプラクティス、設計原則について解説しています。Goプログラマーがより効果的でGoらしいコードを書くための指針を提供します。 -
fmt
パッケージとfmt.Stringer
インターフェース:fmt
パッケージは、Go言語におけるフォーマットされたI/O(入出力)を提供します。fmt.Printf
やfmt.Sprintf
などの関数が含まれます。fmt.Stringer
インターフェースは、String() string
というシグネチャを持つメソッドを定義するインターフェースです。任意の型がこのインターフェースを実装すると、その型の値をfmt
パッケージの関数(例:%s
や%v
フォーマット指定子)で出力する際に、自動的にString()
メソッドが呼び出され、その戻り値が文字列として使用されます。 -
String()
メソッドの再帰問題: カスタム型がfmt.Stringer
インターフェースを実装し、そのString()
メソッド内でfmt.Sprintf
を呼び出す場合、特定のフォーマット指定子(%s
,%q
,%v
,%x
,%X
など)を使用すると、fmt.Sprintf
が再びその型のString()
メソッドを呼び出そうとし、無限再帰に陥る可能性があります。これは、fmt.Sprintf
が引数の型を検査し、fmt.Stringer
インターフェースを実装していればそのString()
メソッドを呼び出すという挙動によるものです。 -
range
キーワード:for ... range
ループは、スライス、配列、文字列、マップ、チャネルを反復処理するために使用されます。- スライス/配列の場合:
for index, value := range collection
の形式で、インデックスと値を取得します。 - マップの場合:
for key, value := range map
の形式で、キーと値を取得します。 - キーやインデックスのみが必要な場合は
for key := range map
のように2番目の変数を省略できます。 - 値のみが必要な場合は、ブランク識別子
_
を使用してfor _, value := range collection
のようにキーやインデックスを破棄できます。
- スライス/配列の場合:
-
ブランク識別子 (
_
): Go言語のブランク識別子_
は、変数を宣言したがその値を使用しない場合に、コンパイラのエラーを回避するために使用されます。例えば、関数の戻り値の一部を無視したい場合や、range
ループでインデックスやキーを無視したい場合などに利用されます。 -
new
キーワード:new
はGo言語の組み込み関数で、指定された型のゼロ値を持つ新しい項目をメモリに割り当て、そのアドレス(ポインタ)を返します。他の言語のnew
とは異なり、メモリを初期化するのではなく、ゼロ値で埋めます。例えば、new(int)
は*int
型のポインタを返し、その指す値は0
に初期化されます。
技術的詳細
このコミットにおける技術的な変更点は多岐にわたりますが、特に重要なのは fmt
パッケージの改善と、それに伴う String()
メソッドの実装の簡素化、そして effective_go.html
における説明の正確性の向上です。
-
fmt
パッケージの「より注意深い」挙動: コミットメッセージにある「fmt
パッケージがより注意深くなった (thefmt
package is more careful)」という記述は、fmt.Sprintf
がカスタム型のString()
メソッドを呼び出す際の内部ロジックが改善されたことを示唆しています。具体的には、fmt.Sprintf
がfmt.Stringer
インターフェースを実装した型を処理する際に、無限再帰を避けるためのより洗練されたチェックを行うようになったと考えられます。これにより、ByteSize
型のString()
メソッドのように、以前は再帰を防ぐために必要だったfloat64()
への明示的な型変換が不要になりました。%f
のような数値フォーマット指定子を使用する場合、fmt.Sprintf
はString()
メソッドを呼び出すのではなく、数値として値をフォーマットするため、元々再帰の問題は発生しませんでしたが、この変更はfmt
パッケージがより堅牢になったことを示しています。 -
String()
メソッドにおけるfmt.Sprintf
の安全な使用:effective_go.html
の変更では、String()
メソッド内でfmt.Sprintf
を呼び出すこと自体は問題ないが、%s
,%q
,%v
,%x
,%X
のような文字列フォーマット指定子を使って、ネストされたSprintf
呼び出しを通じてString()
メソッドが再帰的に呼び出されないように注意する必要がある、と明確に説明されています。ByteSize
の例では%f
を使用しているため安全であると強調されています。これは、fmt
パッケージが改善された後でも、開発者が再帰の可能性を理解し、適切なフォーマット指定子を選択することの重要性を示しています。 -
range
ループの例の拡充と明確化:effective_go.html
では、range
ループの使用例が追加・修正されました。- マップのキーと値の両方を反復処理する例 (
for key, value := range oldMap
) が追加され、マップのコピー方法が示されました。 - マップのキーのみを反復処理する例 (
for key := range m
) が追加され、キーのみが必要な場合の簡潔な記述方法が示されました。 - 値のみを反復処理する例では、以前はマップの例 (
for _, value := range m
) であったものが、配列の例 (for _, value := range array
) に変更され、ブランク識別子_
の使用法がより一般的な文脈で説明されました。これにより、range
がマップと配列/スライスで異なる挙動(マップはキーと値、配列/スライスはインデックスと値)を示すことがより明確になりました。
- マップのキーと値の両方を反復処理する例 (
-
new
キーワードの説明の修正:new
キーワードの説明において、「it only zeroes it.」が「it only zeros it.」に修正されました。これは単なるスペルミスまたは文法的な修正であり、意味的な変更はありませんが、ドキュメントの正確性を高めるための細かな改善です。 -
その他の軽微な修正:
- バイト配列の比較ルーチンのコメントがより詳細になりました。
- スライシングの説明文がより自然な表現に修正されました。
- 並列計算に関する説明で、「
gc
(6g
, etc.)」が「Go runtime」に修正されました。これは、Goのコンパイラ(gc
)ではなく、Goのランタイムが並列処理のスケジューリングを担当するという、より正確な表現です。 - 「webserver」が「web server」に修正されるなど、全体的にドキュメントの表現が洗練されています。
これらの変更は、Go言語のドキュメントの品質を向上させ、開発者がより正確で最新の情報を得られるようにするための継続的な取り組みの一環です。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、doc/effective_go.html
と doc/progs/eff_bytesize.go
の2つのファイルにわたります。
doc/effective_go.html
の変更
effective_go.html
では、主に以下のセクションが変更されています。
-
range
ループに関する説明の追加と修正:--- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -625,9 +625,28 @@ or reading from a channel, a <code>range</code> clause can manage the loop. </p> <pre> -var m map[string]int +for key, value := range oldMap { + newMap[key] = value +} +</pre> + +<p> +If you only need the first item in the range (the key or index), drop the second: +</p> +<pre> +for key := range m { + if expired(key) { + delete(m, key) + }\n +}\n +</pre> +\n +<p> +If you only need the second item in the range (the value), use the <em>blank identifier</em>, an underscore, to discard the first:\n +</p>\n +<pre>\n sum := 0 -for _, value := range m { // key is unused +for _, value := range array { sum += value } </pre>
-
new
キーワードの説明の修正:--- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -1003,7 +1022,7 @@ but the rules are simple.\n Let\'s talk about <code>new</code> first.\n It\'s a built-in function that allocates memory, but unlike its namesakes\n in some other languages it does not <em>initialize</em> the memory,\n-it only <em>zeroes</em> it.\n+it only <em>zeros</em> it.\n That is,\n <code>new(T)</code> allocates zeroed storage for a new item of type\n <code>T</code> and returns its address, a value of type <code>*T</code>.\n ```
-
String()
メソッドとfmt.Sprintf
の再帰に関する説明の追加:--- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -1697,13 +1716,20 @@ automatically for printing, even as part of a general type.\n </p>\n {{code \"/doc/progs/eff_bytesize.go\" `/^func.*ByteSize.*String/` `/^}/`}}\n <p>\n-(The <code>float64</code> conversions prevent <code>Sprintf</code> \n-from recurring back through the <code>String</code> method for \n-<code>ByteSize</code>.)\n+<p>\n+Note that it\'s fine to call <code>Sprintf</code> and friends in the\n+implementation of <code>String</code> methods, but beware of\n+recurring into the <code>String</code> method through the nested\n+<code>Sprintf</code> call using a string format\n+(<code>%s</code>, <code>%q</code>, <code>%v</code>, <code>%x</code> or <code>%X</code>).\n+The <code>ByteSize</code> implementation of <code>String</code> is safe\n+because it calls <code>Sprintf</code> with <code>%f</code>.\n+</p>\n+\n <h3 id=\"variables\">Variables</h3>\n \n <p>\n ```
doc/progs/eff_bytesize.go
の変更
eff_bytesize.go
では、ByteSize
型の String()
メソッドから float64()
への明示的な型変換が削除されています。
--- a/doc/progs/eff_bytesize.go
+++ b/doc/progs/eff_bytesize.go
@@ -23,23 +23,23 @@ const (
func (b ByteSize) String() string {
switch {
case b >= YB:
- return fmt.Sprintf("%.2fYB", float64(b/YB))
+ return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
- return fmt.Sprintf("%.2fZB", float64(b/ZB))
+ return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
- return fmt.Sprintf("%.2fEB", float64(b/EB))
+ return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
- return fmt.Sprintf("%.2fPB", float64(b/PB))
+ return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
- return fmt.Sprintf("%.2fTB", float64(b/TB))
+ return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
- return fmt.Sprintf("%.2fGB", float64(b/GB))
+ return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
- return fmt.Sprintf("%.2fMB", float64(b/MB))
+ return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
- return fmt.Sprintf("%.2fKB", float64(b/KB))
+ return fmt.Sprintf("%.2fKB", b/KB)
}
- return fmt.Sprintf("%.2fB", float64(b))
+ return fmt.Sprintf("%.2fB", b)
}
func main() {
コアとなるコードの解説
doc/effective_go.html
の変更解説
-
range
ループの例の拡充: 追加されたマップの反復処理の例は、for key, value := range oldMap
の形式でマップのキーと値の両方を取得できることを示しています。これは、マップの要素を別のマップにコピーする一般的なパターンです。for key := range m
の例は、マップのキーのみが必要な場合に、2番目の変数を省略できることを示しています。これは、マップ内に特定のキーが存在するかどうかをチェックし、そのキーに関連する操作(例:delete(m, key)
) を行う際によく使われます。for _, value := range array
への変更は、ブランク識別子_
の使用法をより明確にしています。以前の例ではマップに対して_
を使ってキーを無視していましたが、新しい例では配列に対してインデックスを無視して値のみを処理するパターンを示しています。これにより、range
が配列/スライスとマップで異なる戻り値を持つこと、そして_
が不要な要素を破棄するために汎用的に使えることが強調されます。 -
new
キーワードの説明の修正: 「zeroes
」から「zeros
」への修正は、単なる英語のスペル修正です。Go言語のnew
関数がメモリをゼロ値で初期化するという本質的な意味は変わりません。 -
String()
メソッドとfmt.Sprintf
の再帰に関する説明の追加: この変更は、fmt
パッケージの改善を反映した最も重要な部分です。以前はByteSize
のString()
メソッドでfloat64
への明示的な型変換が必要だった理由が、Sprintf
がString()
メソッドを再帰的に呼び出すのを防ぐためであると説明されていました。 新しい説明では、String()
メソッド内でSprintf
を呼び出すこと自体は問題ないが、%s
,%q
,%v
,%x
,%X
のような「文字列フォーマット」指定子を使用すると、Sprintf
が再びString()
メソッドを呼び出し、無限再帰に陥る可能性があると警告しています。そして、ByteSize
の例が%f
(浮動小数点数フォーマット) を使用しているため安全であると明確に述べています。これは、fmt
パッケージがカスタム型を処理する際に、文字列としてフォーマットする必要がある場合にのみString()
メソッドを呼び出すという、より洗練された挙動をするようになったことを示唆しています。開発者は、String()
メソッド内でSprintf
を使用する際に、再帰を引き起こす可能性のあるフォーマット指定子を避けるべきであるという重要なガイダンスが提供されています。
doc/progs/eff_bytesize.go
の変更解説
ByteSize
型の String()
メソッドから float64()
への明示的な型変換がすべて削除されました。
func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB) // 以前は float64(b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB) // 以前は float64(b/ZB)
// ... 他のケースも同様
}
return fmt.Sprintf("%.2fB", b) // 以前は float64(b)
}
この変更は、fmt
パッケージが ByteSize
型を %f
フォーマット指定子で直接処理できるようになったことを示しています。ByteSize
は基底型が数値型(float64
)であるため、fmt.Sprintf
は String()
メソッドを呼び出すことなく、直接数値としてフォーマットできます。以前の float64()
への変換は、fmt
パッケージの挙動がまだ洗練されていなかった時期の回避策であり、このコミットによってその回避策が不要になったことを示しています。これにより、コードがより簡潔になり、fmt
パッケージの進化が反映されています。
関連リンク
- Effective Go (Go言語公式ドキュメント)
- fmt パッケージ (Go言語公式ドキュメント)
- The Go Programming Language Specification - Conversions
- The Go Programming Language Specification - For statements
- The Go Programming Language Specification - Blank identifier
- The Go Programming Language Specification - Allocations
参考にした情報源リンク
- GitHub: golang/go commit 4074795e151813f303d5500d255901c6a3a796ef
- Gerrit Code Review: golang.org/cl/5907047 (Goの変更リスト)
- Go言語の公式ドキュメント (
effective_go
およびfmt
パッケージのドキュメント) - Go言語の
new
キーワード、range
ループ、ブランク識別子に関する一般的な知識