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

[インデックス 13825] ファイルの概要

このコミットは、Go言語の実験的なロケール照合パッケージ exp/locale/collate のAPIに重要な変更を加えています。主な変更点は、ロケールごとの照合オブジェクト(Collator)の取得方法を、グローバル変数から New() 関数を介した動的な生成に切り替えたことです。これにより、API利用者が Collator オブジェクトの生成数を最小限に抑え、再利用を促進するとともに、将来的に特定のロケール向けに最適化された照合テーブルのオンデマンド初期化を可能にする基盤を築いています。

また、内部的な変更として、共有される照合テーブルの変数名が root* から main* に変更され、サポートされているロケールの一覧を取得するための Locales() メソッドが追加されました。これらの変更は、パッケージの柔軟性とスケーラビリティを向上させることを目的としています。

コミット

commit a4d08ed5dfe23f5b0d777548410456fbb517478c
Author: Marcel van Lohuizen <mpvl@golang.org>
Date:   Fri Sep 14 19:10:02 2012 +0900

    exp/locale/collate: changed API to allow access to different locales through New(),
    instead of variables. Several reasons:
    - Encourage users of the API to minimize the number of creations and reuse Collate objects.
    - Don't rule out the possibility of using initialization code for collators. For some locales
      it will be possible to have very compact representations that can be quickly expanded
      into a proper table on demand.
    Other changes:
    - Change name of root* vars to main*, as the tables are shared between locales.
    - Added Locales() method to get a list of supported locales.

    R=r
    CC=golang-dev
    https://golang.org/cl/6498107

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a4d08ed5dfe23f5b0d777548410456fbb517478c

元コミット内容

exp/locale/collate パッケージのAPIが変更され、異なるロケールへのアクセスが変数ではなく New() 関数を通じて可能になりました。これにはいくつかの理由があります。

  • APIの利用者が Collate オブジェクトの生成数を最小限に抑え、再利用することを奨励するため。
  • 照合器(collator)の初期化コードを使用する可能性を排除しないため。一部のロケールでは、非常にコンパクトな表現を持ち、必要に応じて適切なテーブルに素早く展開できる可能性があります。

その他の変更点:

  • root* 変数名を main* に変更しました。これは、テーブルがロケール間で共有されるためです。
  • サポートされているロケールの一覧を取得するための Locales() メソッドを追加しました。

変更の背景

このコミットの背景には、exp/locale/collate パッケージの設計思想の進化があります。以前のバージョンでは、特定のロケールに対応する Collator オブジェクトがグローバル変数として提供されていた可能性があります。しかし、このアプローチには以下の課題がありました。

  1. リソース効率の悪さ: グローバル変数として Collator オブジェクトが提供される場合、アプリケーションが使用しないロケールの Collator もメモリ上に存在し続ける可能性があります。また、必要になるたびに新しい Collator を生成するような利用パターンを抑制しにくいという問題がありました。
  2. 初期化の柔軟性の欠如: グローバル変数としての提供は、Collator オブジェクトの初期化プロセスに柔軟性を持たせにくいという制約がありました。特に、大規模な照合テーブルを持つロケールの場合、アプリケーションの起動時にすべてのテーブルをロードすることは、起動時間の増加やメモリ消費の増大につながります。オンデマンドでの初期化や、よりコンパクトなデータ表現からの展開といった最適化を導入するためには、動的な生成メカニズムが必要でした。
  3. 命名の一貫性: 照合テーブルが複数のロケール間で共有されるにもかかわらず、root* という名前が使われているのは、その実態を正確に反映していませんでした。より汎用的な main* という名前に変更することで、コードの意図が明確になります。
  4. サポートロケールの可視性: ユーザーが利用可能なロケールをプログラム的に知る手段が不足していました。Locales() メソッドの追加は、この情報へのアクセスを提供し、APIの使いやすさを向上させます。

これらの課題に対処するため、APIを New() 関数ベースの動的な生成に移行し、内部的な命名規則を改善し、サポートロケールの一覧を提供する機能が追加されました。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1. ロケール (Locale)

ロケールとは、ユーザーの言語、地域、文化的な慣習を定義する一連のパラメータのことです。これには、日付と時刻のフォーマット、通貨記号、数字の表示方法、そして文字列のソート順序(照合順序)などが含まれます。例えば、"en_US" はアメリカ英語のロケールを、"ja_JP" は日本語のロケールを指します。

2. 照合 (Collation)

照合とは、文字列を特定のロケールや言語の規則に従ってソート(並べ替え)するプロセスのことです。単純な文字コード順のソートとは異なり、照合は以下のような複雑なルールを考慮します。

  • アクセント記号の扱い: 例えば、フランス語では "e", "é", "è", "ê" などが異なる文字として扱われる場合がありますが、ソート時には同じ文字のバリエーションとして扱われることがあります。
  • 大文字・小文字の区別: 大文字と小文字を区別するかどうか、またはどの程度の優先順位で区別するか。
  • 結合文字: 複数の文字が組み合わさって一つの意味を持つ文字(例: ドイツ語の "ß" と "ss")。
  • 無視される文字: ソート順に影響を与えない文字(例: ハイフンやアポストロフィ)。
  • 言語固有の順序: 例えば、スウェーデン語では "å", "ä", "ö" がアルファベットの最後に位置します。

3. Unicode Collation Algorithm (UCA)

Unicode Collation Algorithm (UCA) は、Unicode Consortiumによって定義された、多言語環境での文字列照合のための標準アルゴリズムです。UCAは、言語やロケールに依存しない基本的な照合順序を提供し、さらにロケール固有のカスタマイズ(Tailoring)を可能にします。UCAは、文字列を比較するために「照合要素(collation elements)」と呼ばれる数値のシーケンスに変換し、これらのシーケンスを比較することでソート順を決定します。

4. Go言語の exp/locale/collate パッケージ

exp/locale/collate は、Go言語の実験的なパッケージであり、Unicode Collation Algorithm (UCA) に基づいて文字列の照合機能を提供します。このパッケージは、異なるロケールにおける文字列の正しいソート順序を決定するために使用されます。Collator オブジェクトは、特定のロケールと照合設定(例: 大文字・小文字の区別、アクセント記号の扱いなど)に基づいて文字列を比較するためのメソッドを提供します。

5. API (Application Programming Interface)

APIは、ソフトウェアコンポーネントが互いに通信するためのインターフェースのセットです。このコミットでは、exp/locale/collate パッケージの外部から利用される関数や構造体の変更、特に New() 関数や Locales() メソッドの導入がAPIの変更に該当します。

6. 正規化形式 (Normalization Forms)

Unicodeには、同じ文字を異なる方法で表現できる場合があります(例: アクセント付き文字を単一のコードポイントで表現するか、基本文字と結合文字の組み合わせで表現するか)。正規化形式は、これらの異なる表現を標準的な形式に変換するプロセスです。このコミットでは norm.NFD (Normalization Form Canonical Decomposition) が言及されており、これは文字をその構成要素に分解する正規化形式です。照合処理の前に文字列を正規化することで、異なる表現を持つ同じ文字が正しく比較されるようになります。

技術的詳細

このコミットにおける技術的な変更は、exp/locale/collate パッケージの内部構造と外部APIの両方に影響を与えています。

1. Collator オブジェクトの取得方法の変更 (New() 関数の導入)

最も重要な変更は、Collator オブジェクトの取得方法が、グローバル変数(例: collate.Root)から collate.New(loc string) *Collator 関数に移行したことです。

  • 旧方式: collate.Root のようなグローバル変数を直接参照することで、ルートロケール(デフォルトの照合順序)の Collator を取得していました。他のロケールについては、同様にロケールごとのグローバル変数が存在した可能性があります。
  • 新方式: New(loc string) 関数にロケール識別子(例: "en_US", "ja")を渡すことで、そのロケールに対応する Collator オブジェクトが返されます。引数に空文字列 "" を渡すか、サポートされていないロケールを渡した場合は、デフォルトの mainTable(旧 rootTable)に基づく Collator が返されます。

この変更の利点は以下の通りです。

  • リソース管理の改善: ユーザーは必要なロケールの Collator のみを生成し、不要な Collator オブジェクトがメモリを消費するのを防ぐことができます。また、一度生成した Collator オブジェクトを再利用することで、オブジェクト生成のオーバーヘッドを削減できます。
  • オンデマンド初期化の可能性: New() 関数内部で、特定のロケールに対応する照合テーブルをオンデマンドでロードしたり、コンパクトな表現から展開したりするロジックを実装できるようになります。これにより、アプリケーションの起動時間を短縮し、メモリフットプリントを最適化する柔軟性が生まれます。
  • APIの一貫性: 多くのライブラリでリソースを取得する際に New プレフィックスの関数を使用するパターンが一般的であり、APIの一貫性が向上します。

2. root* 変数から main* 変数への名称変更

照合テーブルを構成する内部データ構造(rootExpandElem, rootContractElem, rootValues, rootLookup, rootCTEntries など)の変数名が、root* から main* に変更されました。

  • 理由: コミットメッセージにある通り、「テーブルがロケール間で共有されるため」です。root という名前は、特定の「ルートロケール」に限定されるような印象を与えますが、実際にはこれらのテーブルは多くのロケールの照合の基盤として機能します。main というより一般的な名前に変更することで、その役割がより正確に表現されます。

3. Locales() メソッドの追加

func Locales() []string メソッドが追加され、パッケージがサポートするロケール識別子のリストを返します。

  • 目的: アプリケーション開発者が、実行時に利用可能な照合ロケールをプログラム的に取得できるようにするためです。これにより、ユーザーインターフェースでロケール選択肢を提供したり、ロケール設定のバリデーションを行ったりする際に役立ちます。
  • 実装: src/pkg/exp/locale/collate/tables.go で定義された availableLocales という文字列スライスが返されます。このスライスは、ビルド時に生成される照合テーブルに含まれるロケールIDのリストです。

4. tableIndex 構造体の導入と indexedTable メソッド

src/pkg/exp/locale/collate/table.gotableIndex 構造体が導入され、table 構造体に indexedTable メソッドが追加されました。

  • tableIndex 構造体:
    type tableIndex struct {
        lookupOffset uint32
        valuesOffset uint32
    }
    
    これは、mainTable の中で特定のロケールに対応する照合データがどこから始まるかを示すオフセット情報を保持します。
  • indexedTable メソッド:
    func (t *table) indexedTable(idx tableIndex) *table {
        nt := *t
        nt.index.index0 = t.index.index[idx.lookupOffset*blockSize:]
        nt.index.values0 = t.index.values[idx.valuesOffset*blockSize:]
        return &nt
    }
    
    このメソッドは、既存の tablemainTable)を基に、tableIndex で指定されたオフセットから始まる部分を指す新しい table オブジェクトを生成します。これにより、異なるロケールが mainTable の一部を共有しつつ、それぞれのロケール固有の照合ルールを効率的に参照できるようになります。

これらの変更は、exp/locale/collate パッケージがより柔軟で効率的なロケール照合機能を提供するための重要なステップです。特に、将来的なパフォーマンス最適化やメモリ使用量の削減に向けた基盤を構築しています。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/exp/locale/collate/collate.go:

    • Locales() 関数と New() 関数が追加されました。
    • Collator 構造体の取得方法が変更されました。
    • testCollator 関数内で collate.Root の代わりに collate.New("") が使用されるようになりました。
  2. src/pkg/exp/locale/collate/tables.go:

    • availableLocales 変数(サポートされるロケールIDのリスト)が追加されました。
    • locales マップ(ロケールIDと tableIndex のマッピング)が追加されました。
    • _Root 変数と Root 変数が削除され、mainTable 変数が導入されました。
    • rootExpandElem, rootContractElem, rootValues, rootLookup, rootCTEntries といった変数名がそれぞれ mainExpandElem, mainContractElem, mainValues, mainLookup, mainCTEntries に変更されました。
  3. src/pkg/exp/locale/collate/table.go:

    • tableIndex 構造体が定義されました。
    • table 構造体に indexedTable() メソッドが追加されました。
  4. src/pkg/exp/locale/collate/build/builder.go:

    • Builder.Print() 関数が変更され、availableLocaleslocales マップを生成するコードが追加されました。
    • t.fprint(w, "root")t.fprint(w, "main") に変更されました。
  5. src/pkg/exp/locale/collate/build/table.go:

    • table.fprintIndex() 関数が追加され、tableIndex 構造体の内容をGoコードとして出力するようになりました。
  6. src/pkg/exp/locale/collate/maketables.go:

    • printCollators 関数が削除されました。これは、Collator オブジェクトがグローバル変数として定義されなくなったため不要になりました。
    • main 関数内で、printCollators(c) の呼び出しが削除されました。

コアとなるコードの解説

src/pkg/exp/locale/collate/collate.go

// Locales returns the list of locales for which collating differs from its parent locale.
func Locales() []string {
	return availableLocales
}

// New returns a new Collator initialized for the given locale.
func New(loc string) *Collator {
	// TODO: handle locale selection according to spec.
	t := &mainTable
	if loc != "" {
		if idx, ok := locales[loc]; ok {
			t = mainTable.indexedTable(idx)
		}
	}
	return &Collator{
		Strength: Quaternary,
		f:        norm.NFD,
		t:        t,
	}
}
  • Locales(): tables.go で定義されている availableLocales スライスをそのまま返します。これにより、外部からサポートされているロケールの一覧を取得できます。
  • New(loc string):
    • デフォルトの照合テーブルとして mainTable を設定します。
    • loc が空文字列でない場合、locales マップから対応する tableIndex を検索します。
    • tableIndex が見つかった場合、mainTable.indexedTable(idx) を呼び出して、そのロケール固有の照合データを持つ新しい table オブジェクトを生成し、t に設定します。
    • 最終的に、設定された table を持つ新しい Collator オブジェクトを返します。StrengthQuaternary(4レベルの照合)、fnorm.NFD(正規化形式)に設定されています。

src/pkg/exp/locale/collate/tables.go

var availableLocales = []string{"af", "ar", ..., "zh"} // 多数のロケールID
var locales = map[string]tableIndex{
	"af": tableIndex{
		lookupOffset: 0x13,
		valuesOffset: 0x0,
	},
	// ... 他のロケールエントリ ...
	"root": tableIndex{
		lookupOffset: 0x13,
		valuesOffset: 0x0,
	},
	// ...
}

var mainTable = table{
	trie{mainLookup[1216:], mainValues[0:], mainLookup[:], mainValues[:]},\
	mainExpandElem[:],\
	contractTrieSet(mainCTEntries[:]),\
	mainContractElem[:],\
	9,\
	0x2ED,\
}

// root* 変数から main* 変数への名称変更
var mainExpandElem = [4642]uint32{...}
var mainContractElem = [799]uint32{...}
var mainValues = [25408]uint32{...}
var mainLookup = [1472]uint16{...}
var mainCTEntries = [126]struct{ l, h, n, i uint8 }{...}
  • availableLocales: collate.Locales() 関数によって返される、サポートされているロケールIDのリストです。これはビルド時に生成されます。
  • locales: 各ロケールIDをキーとし、そのロケールに対応する照合データが mainTable 内のどこに位置するかを示す tableIndex 構造体を値とするマップです。これもビルド時に生成されます。
  • mainTable: 照合の主要なデータを含む table 構造体です。以前の rootTable に相当し、その内部のデータ配列名も root* から main* に変更されています。このテーブルは、すべてのロケールの照合の基盤となります。

src/pkg/exp/locale/collate/table.go

// tableIndex holds information for constructing a table
// for a certain locale based on the main table.
type tableIndex struct {
	lookupOffset uint32
	valuesOffset uint32
}

func (t *table) indexedTable(idx tableIndex) *table {
	nt := *t
	nt.index.index0 = t.index.index[idx.lookupOffset*blockSize:]
	nt.index.values0 = t.index.values[idx.valuesOffset*blockSize:]
	return &nt
}
  • tableIndex: lookupOffsetvaluesOffset を持ち、mainTable 内の特定のロケールに特化した照合データの開始位置を示します。
  • indexedTable(idx tableIndex) *table:
    • 現在の table オブジェクト(通常は mainTable)のコピー nt を作成します。
    • nt.index.index0nt.index.values0 を、idx で指定されたオフセットから始まる mainTable のデータスライスに再設定します。これにより、ntmainTable の一部を「ビュー」として参照するようになります。
    • この新しい table オブジェクト nt を返します。これにより、メモリを複製することなく、異なるロケールが共有の mainTable の異なる部分を参照できるようになります。

src/pkg/exp/locale/collate/build/builder.go

func (b *Builder) Print(w io.Writer) (n int, err error) {
	// ...
	p(fmt.Fprintf(w, "var availableLocales = []string{\"))
	for _, loc := range b.locale {
		p(fmt.Fprintf(w, "%q, ", loc.id))
	}
	p(fmt.Fprintln(w, "}\\n\"))
	p(fmt.Fprintln(w, "var locales = map[string]tableIndex{\"))
	for _, loc := range b.locale {
		p(fmt.Fprintf(w, "\t%q: ", loc.id))
		p(t.fprintIndex(w, loc.index.handle))
		p(fmt.Fprintln(w, ",\"))
	}
	p(fmt.Fprint(w, "}\\n\\n\"))
	n, _, err = t.fprint(w, "main") // "root" から "main" に変更
	return
}
  • Builder.Print(): この関数は、照合テーブルのGoソースコードを生成します。変更点として、availableLocales スライスと locales マップのGoコードを生成するロジックが追加されました。これにより、tables.go ファイルが動的に生成される際に、これらのデータ構造が適切に初期化されます。
  • t.fprint(w, "main"): 生成されるテーブルの変数名が root から main に変更されたことに対応しています。

これらのコード変更は、exp/locale/collate パッケージがよりモジュール化され、効率的で、将来の拡張に対応できるような設計へと進化していることを示しています。特に、New() 関数と tableIndexindexedTable() の組み合わせは、ロケールごとの照合データを効率的に管理し、オンデマンドでのリソース利用を可能にするための重要なパターンです。

関連リンク

参考にした情報源リンク