[インデックス 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
オブジェクトがグローバル変数として提供されていた可能性があります。しかし、このアプローチには以下の課題がありました。
- リソース効率の悪さ: グローバル変数として
Collator
オブジェクトが提供される場合、アプリケーションが使用しないロケールのCollator
もメモリ上に存在し続ける可能性があります。また、必要になるたびに新しいCollator
を生成するような利用パターンを抑制しにくいという問題がありました。 - 初期化の柔軟性の欠如: グローバル変数としての提供は、
Collator
オブジェクトの初期化プロセスに柔軟性を持たせにくいという制約がありました。特に、大規模な照合テーブルを持つロケールの場合、アプリケーションの起動時にすべてのテーブルをロードすることは、起動時間の増加やメモリ消費の増大につながります。オンデマンドでの初期化や、よりコンパクトなデータ表現からの展開といった最適化を導入するためには、動的な生成メカニズムが必要でした。 - 命名の一貫性: 照合テーブルが複数のロケール間で共有されるにもかかわらず、
root*
という名前が使われているのは、その実態を正確に反映していませんでした。より汎用的なmain*
という名前に変更することで、コードの意図が明確になります。 - サポートロケールの可視性: ユーザーが利用可能なロケールをプログラム的に知る手段が不足していました。
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.go
に tableIndex
構造体が導入され、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 }
table
(mainTable
)を基に、tableIndex
で指定されたオフセットから始まる部分を指す新しいtable
オブジェクトを生成します。これにより、異なるロケールがmainTable
の一部を共有しつつ、それぞれのロケール固有の照合ルールを効率的に参照できるようになります。
これらの変更は、exp/locale/collate
パッケージがより柔軟で効率的なロケール照合機能を提供するための重要なステップです。特に、将来的なパフォーマンス最適化やメモリ使用量の削減に向けた基盤を構築しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
src/pkg/exp/locale/collate/collate.go
:Locales()
関数とNew()
関数が追加されました。Collator
構造体の取得方法が変更されました。testCollator
関数内でcollate.Root
の代わりにcollate.New("")
が使用されるようになりました。
-
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
に変更されました。
-
src/pkg/exp/locale/collate/table.go
:tableIndex
構造体が定義されました。table
構造体にindexedTable()
メソッドが追加されました。
-
src/pkg/exp/locale/collate/build/builder.go
:Builder.Print()
関数が変更され、availableLocales
とlocales
マップを生成するコードが追加されました。t.fprint(w, "root")
がt.fprint(w, "main")
に変更されました。
-
src/pkg/exp/locale/collate/build/table.go
:table.fprintIndex()
関数が追加され、tableIndex
構造体の内容をGoコードとして出力するようになりました。
-
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
オブジェクトを返します。Strength
はQuaternary
(4レベルの照合)、f
はnorm.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
:lookupOffset
とvaluesOffset
を持ち、mainTable
内の特定のロケールに特化した照合データの開始位置を示します。indexedTable(idx tableIndex) *table
:- 現在の
table
オブジェクト(通常はmainTable
)のコピーnt
を作成します。 nt.index.index0
とnt.index.values0
を、idx
で指定されたオフセットから始まるmainTable
のデータスライスに再設定します。これにより、nt
はmainTable
の一部を「ビュー」として参照するようになります。- この新しい
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()
関数と tableIndex
、indexedTable()
の組み合わせは、ロケールごとの照合データを効率的に管理し、オンデマンドでのリソース利用を可能にするための重要なパターンです。
関連リンク
- Unicode Collation Algorithm (UCA)
- Go言語の
exp
パッケージについて (Goの実験的なパッケージに関する一般的な情報)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Unicode Consortium
- Goのコードレビューシステム (Gerrit) のCL (Change-list) 6498107 (コミットメッセージに記載されているリンク)
- Goの
exp/locale/collate
パッケージのドキュメント (現在のパッケージのドキュメント。コミット当時のものではない可能性がありますが、概念理解に役立ちます) - Goの
exp/norm
パッケージのドキュメント (正規化に関する情報)