[インデックス 10369] Goコンパイラ:関数比較の回避
コミット
コミットハッシュ: 558e7fc33279d2f43ef8253d39c828ca7cae3d8a 作成者: Russ Cox rsc@golang.org 日付: 2011年11月13日 22:57:19 -0500 コミットメッセージ: various: avoid func compare
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/558e7fc33279d2f43ef8253d39c828ca7cae3d8a
元コミット内容
このコミットは、Goの複数のパッケージにわたって関数比較を回避するための重要な修正を実装しています。9つのファイルが変更され、136行の追加と107行の削除が行われました。
変更対象ファイル:
src/cmd/gofix/main_test.go
src/cmd/gofix/typecheck.go
src/pkg/bytes/bytes_test.go
src/pkg/compress/lzw/writer.go
src/pkg/strings/strings_test.go
src/pkg/encoding/json/decode.go
src/pkg/encoding/json/scanner.go
src/pkg/go/ast/filter.go
src/pkg/net/ipsock.go
変更の背景
2011年当時、Go言語はまだ1.0リリース前の開発段階にあり、言語仕様の根本的な部分が固まりつつありました。このコミットは、Go言語の重要な設計原則の一つである「関数値の比較禁止」を実装全体に一貫して適用するための修正です。
Go言語では、関数値(function values)を直接比較することができません。これは言語仕様で明確に規定されており、関数、スライス、マップの値はnilとのみ比較可能で、相互の比較は許可されていません。この制限には重要な技術的理由があります:
- クロージャの曖昧性: 異なる環境変数を持つクロージャを等しいとするかどうかが曖昧
- パフォーマンス最適化: 関数比較の禁止により、コンパイラは単一の実装を生成可能
- メモリ効率: 実行時に新しいクロージャを生成する必要がない
前提知識の解説
関数値の比較について
Go言語の仕様では、関数値は以下の理由で比較できません:
// 許可されない例
func main() {
f1 := func() {}
f2 := func() {}
if f1 == f2 { // コンパイルエラー:invalid operation: f1 == f2 (func can only be compared to nil)
// ...
}
}
// 許可される例
func main() {
var f func()
if f == nil { // OK
// ...
}
}
2011年のGo言語の開発状況
- Go 1.0リリース(2012年3月)の約4か月前
- 言語仕様の最終調整段階
- 標準ライブラリの設計固化
- パフォーマンス最適化の実装
技術的詳細
このコミットは、以下の技術的変更を実装しています:
1. テストコードの関数比較回避
bytes_test.go
とstrings_test.go
では、テストデータ構造体から関数ポインタを削除し、文字列による関数識別に変更:
// 修正前
type TrimTest struct {
f func([]byte, string) []byte
in, cutset, out string
}
// 修正後
type TrimTest struct {
f string
in, cutset, out string
}
2. JSON scannerの状態管理改善
encoding/json/scanner.go
では、関数比較の代わりにブール値による状態管理を導入:
// 修正前
if s.step == stateRedo {
panic("invalid use of scanner")
}
// 修正後
if s.redo {
panic("json: invalid use of scanner")
}
3. ASTフィルタリング機能の最適化
go/ast/filter.go
では、関数比較を使用した条件分岐を、明示的なブール値パラメータに置き換え:
// 修正前
if filter == exportFilter {
filterType(f.Type, filter)
}
// 修正後
if export {
filterType(f.Type, filter, export)
}
4. ネットワーク関数の改良
net/ipsock.go
では、関数比較を使用したフィルタリングをnil比較に変更:
// 修正前
if filter == anyaddr {
// ...
}
// 修正後
if filter == nil {
// ...
}
コアとなるコードの変更箇所
1. bytes/bytes_test.go:98-172
最も重要な変更の一つは、テストデータ構造体における関数ポインタの文字列への置換です:
// 修正前(行99)
f func([]byte, string) []byte
// 修正後(行100)
f string
テストデータ配列も関数ポインタから文字列に変更:
// 修正前(行105)
{Trim, "abba", "a", "bb"},
// 修正後(行115)
{"Trim", "abba", "a", "bb"},
2. encoding/json/scanner.go:240-303
JSON scannerに新しいブール値フィールドを追加:
// 修正後(行240)
redo bool
状態チェックを関数比較からブール値比較に変更:
// 修正前(行290)
if s.step == stateRedo {
// 修正後(行292)
if s.redo {
3. go/ast/filter.go:334-496
ASTフィルタリング関数にexportパラメータを追加し、関数比較を削除:
// 修正前(行333)
func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) {
// 修正後(行334)
func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) {
条件分岐の変更:
// 修正前(行342)
if filter == exportFilter {
// 修正後(行344)
if export {
コアとなるコードの解説
テストコードの改良
bytes_test.go
とstrings_test.go
の変更は、テストの可読性とメンテナンス性を向上させます。関数ポインタの代わりに文字列を使用することで:
- デバッグの容易さ: エラーメッセージで関数名が明確に表示される
- テストデータの可視性: テストケースの内容が一目で理解できる
- 実行時の安全性: 関数比較によるパニックを回避
JSON scannerの最適化
JSON scannerの変更は、パフォーマンスと安全性の両面で改善をもたらします:
- 高速化: 関数比較よりもブール値比較の方が高速
- メモリ効率: 不要な関数ポインタの保持を回避
- 明確性: 状態管理がより明示的になる
ASTフィルタリングの汎用化
AST filerの変更により、エクスポートフィルタリングが以下のように改善されます:
- コードの重複削減: 共通の内部関数を使用
- パフォーマンス向上: 関数比較を避けることで高速化
- 拡張性: 新しいフィルタリング方式の追加が容易