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

[インデックス 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つのメソッドから構成されます。

  1. Len() int: ソート対象の要素数を返します。
  2. Swap(i, j int): インデックスijの要素を入れ替えます。
  3. 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パッケージが利用されました。具体的には、以下の手順でソートが実装されています。

  1. sortパッケージをインポートします。
  2. sort.Interfaceインターフェースを実装する新しい型exampleByNameを定義します。この型は[]*Exampleを基盤とします。
  3. 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が名前のアルファベット順にソートされるようになります。
  4. 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ファイルに集中しています。

  1. import "sort" の追加: Examples関数内でスライスをソートするために、Goの標準ライブラリであるsortパッケージが新しくインポートされました。

    import (
        "go/token"
        "regexp"
        "sort" // 追加
        "strings"
        "unicode"
        "unicode/utf8"
    )
    
  2. 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)に基づいてソートが実行されます。

  3. 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の名前の昇順でソートされることが保証されるようになりました。

関連リンク

参考にした情報源リンク