[インデックス 11952] ファイルの概要
このコミットは、Go言語のgo/ast
パッケージにおいて、Examples
関数が返すExample
スライスの順序を、名前順にソートされるように変更するものです。これにより、go/ast
パッケージを利用してGoのソースコードから抽出されるExampleのリストが、常に予測可能で一貫した順序で提供されるようになります。
コミット
- コミットハッシュ:
e9016bb8a7e5cf173556c51a173ae04f91da168a
- Author: Andrew Gerrand adg@golang.org
- Date: Thu Feb 16 13:08:35 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e9016bb8a7e5cf173556c51a173ae04f91da168a
元コミット内容
go/ast: return Examples in name order
R=bradfitz
CC=golang-dev
https://golang.org/cl/5673061
変更の背景
Go言語のgo/ast
パッケージは、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析するための機能を提供します。このパッケージには、テストファイル内のExample関数(Example_MyFunction
のような形式の関数)を抽出するExamples
関数が含まれています。
このコミットが導入される前は、Examples
関数が返すExample
構造体のスライス(リスト)の順序が保証されていませんでした。これは、ファイルシステムからの読み込み順序や、内部的な処理順序に依存する可能性があり、結果として同じソースコードからExamples
関数を呼び出しても、異なる順序でExampleが返されることがありました。
このような非決定的な順序は、ツールの動作の一貫性を損ねる可能性があります。例えば、Exampleのリストを表示するツールや、Exampleを処理するスクリプトが、実行ごとに異なる結果を生成してしまうことが考えられます。これを解決し、より予測可能で安定した動作を提供するために、Exampleをその名前(Name
フィールド)に基づいてソートする変更が導入されました。
前提知識の解説
go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードを解析し、その構造を抽象構文木(AST)として表現するための標準ライブラリです。ASTは、プログラムの構造を木構造で表現したもので、コンパイラ、リンター、コードフォーマッター、ドキュメンテーションツールなど、Goのコードをプログラム的に操作する多くのツールで利用されます。
go/ast
パッケージは、go/parser
パッケージと組み合わせて使用されることが多く、go/parser
がソースコードを解析してASTを構築し、go/ast
がそのASTを操作するための型や関数を提供します。
GoのExample関数
Go言語では、テストファイル(_test.go
で終わるファイル)内にExample_MyFunction
のような形式の関数を記述することで、Exampleコードを定義できます。これらのExample関数は、ドキュメンテーションの一部として機能し、go doc
コマンドやpkg.go.devなどのGoの公式ドキュメンテーションサイトで表示されます。
Example関数は、その関数が示す機能の簡単な使用例を提供し、通常は// Output:
コメントを含んで、そのExampleを実行した際の標準出力の期待値を記述します。go test
コマンドは、これらのExample関数を実行し、// Output:
コメントの内容と実際の出力を比較することで、Exampleが正しく動作するかどうかを検証します。
go/ast
パッケージのExample
構造体は、これらのExample関数に関するメタデータ(名前、関連するドキュメント、コードブロックなど)を保持します。
Goのsort
パッケージとsort.Interface
Go言語の標準ライブラリには、スライスや配列をソートするためのsort
パッケージが提供されています。このパッケージは、特定の型に依存しない汎用的なソート機能を提供するために、sort.Interface
というインターフェースを定義しています。
sort.Interface
インターフェースは、以下の3つのメソッドから構成されます。
Len() int
: ソート対象の要素数を返します。Swap(i, j int)
: インデックスi
とj
の要素を入れ替えます。Less(i, j int) bool
: インデックスi
の要素がインデックスj
の要素よりも小さい(ソート順で前に来る)場合にtrue
を返します。
任意のカスタム型がこのsort.Interface
を実装することで、sort.Sort
関数を使用してその型のスライスをソートできるようになります。
技術的詳細
このコミットの目的は、go/ast
パッケージのExamples
関数が返す[]*Example
スライスを、Example
構造体のName
フィールドに基づいて昇順にソートすることです。
変更前は、Examples
関数は内部的にExampleを収集し、その収集順序は保証されていませんでした。このため、同じGoのソースコードに対してExamples
関数を複数回呼び出すと、Exampleのリストが異なる順序で返される可能性がありました。
この問題を解決するために、Goの標準sort
パッケージが利用されました。具体的には、以下の手順でソートが実装されています。
sort
パッケージをインポートします。sort.Interface
インターフェースを実装する新しい型exampleByName
を定義します。この型は[]*Example
を基盤とします。exampleByName
型に対して、Len()
,Swap(i, j int)
,Less(i, j int) bool
の3つのメソッドを実装します。Len()
はスライスの長さを返します。Swap(i, j int)
はスライスのi
番目とj
番目の要素を交換します。Less(i, j int) bool
は、s[i].Name < s[j].Name
、つまりi
番目のExampleの名前がj
番目のExampleの名前よりも辞書順で小さい場合にtrue
を返します。これにより、Exampleが名前のアルファベット順にソートされるようになります。
Examples
関数内で、Exampleのリストが構築された後、sort.Sort(exampleByName(list))
を呼び出して、このカスタムソートロジックを適用します。
この変更により、Examples
関数は常にExampleを名前順にソートして返すようになり、ツールの動作の一貫性と予測可能性が向上しました。
コアとなるコードの変更箇所
--- a/src/pkg/go/ast/example.go
+++ b/src/pkg/go/ast/example.go
@@ -9,6 +9,7 @@ package ast
import (
"go/token"
"regexp"
+ "sort"
"strings"
"unicode"
"unicode/utf8"
@@ -66,6 +67,7 @@ func Examples(files ...*File) []*Example {
}
list = append(list, flist...)
}
+ sort.Sort(exampleByName(list))
return list
}
@@ -106,3 +108,9 @@ func isTest(name, prefix string) bool {
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
+
+type exampleByName []*Example
+
+func (s exampleByName) Len() int { return len(s) }
+func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
コアとなるコードの解説
このコミットによる変更は、主にsrc/pkg/go/ast/example.go
ファイルに集中しています。
-
import "sort"
の追加:Examples
関数内でスライスをソートするために、Goの標準ライブラリであるsort
パッケージが新しくインポートされました。import ( "go/token" "regexp" "sort" // 追加 "strings" "unicode" "unicode/utf8" )
-
sort.Sort(exampleByName(list))
の呼び出し:Examples
関数内で、すべてのExampleがlist
スライスに収集された後、以下の行が追加されました。func Examples(files ...*File) []*Example { // ... (Example収集ロジック) ... list = append(list, flist...) sort.Sort(exampleByName(list)) // 追加 return list }
ここで、
exampleByName(list)
は、[]*Example
型のlist
スライスを、後述するカスタムソート型exampleByName
に型変換しています。そして、sort.Sort
関数に渡すことで、exampleByName
型が実装するsort.Interface
のメソッド(Len
,Swap
,Less
)に基づいてソートが実行されます。 -
exampleByName
型の定義とsort.Interface
の実装: ファイルの末尾に、exampleByName
という新しい型が定義され、sort.Interface
インターフェースの3つのメソッドが実装されています。type exampleByName []*Example func (s exampleByName) Len() int { return len(s) } func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
type exampleByName []*Example
:exampleByName
は、*Example
のスライスを基盤とする新しい型です。これにより、この型に対してメソッドを定義できるようになります。func (s exampleByName) Len() int { return len(s) }
: スライスの長さを返します。func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
: スライス内の2つの要素を交換します。func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
: ソートの比較ロジックを定義します。s[i]
のName
フィールドがs[j]
のName
フィールドよりも辞書順で小さい場合にtrue
を返します。これにより、ExampleはName
フィールドのアルファベット順にソートされます。
これらの変更により、Examples
関数が返すExampleのリストは、常にExampleの名前の昇順でソートされることが保証されるようになりました。
関連リンク
- Go言語
go/ast
パッケージドキュメント: https://pkg.go.dev/go/ast - Go言語
sort
パッケージドキュメント: https://pkg.go.dev/sort - Go言語のExample関数に関する公式ブログ記事 (Go 1.0 Release Notes - Examples): https://go.dev/doc/go1#examples
参考にした情報源リンク
- Go言語
go/ast
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/go/ast - Go言語
sort
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/sort - Go言語のExample関数に関する情報 (Go Wiki): https://go.dev/wiki/Example
- Go言語の
sort.Interface
に関する解説記事 (例: A Tour of Go - Methods and interfaces): https://go.dev/tour/methods/10 (これは一般的なインターフェースの解説ですが、sort.Interface
の理解に役立ちます) - Go言語の
go test
コマンドに関するドキュメント: https://pkg.go.dev/cmd/go#hdr-Test_packages