Blogの日本語訳を公開しています。Lohuizenはじめに先の記事では、Goでの文字列、バイト、文字について説明していました。けの様々なパッケージの開発に関わってきました。golang.org/x/text/unicode/norm)に焦点を当てたいと思います。このパッケージは、先の文字列に関する記事、そして本記事のタイトルとなっている、文字列の正規化を扱います。正規化は生のバイト列よりも高水準での抽象化を扱います。正規化についてのすべてを知りたければ、Unicode標準の付録15を読むのが良いでしょう。読みやすい記事としては、対応するWikipediaのページ(訳注:日本語版)があります。ここでは、正規化がどのようにGoに関わっているかに焦点を当てます。正規化とは何か同じ文字列を表現するときにいくつかの方法があることがしばしばあります。‘e’のあとにアキュート・アクセントが続いたもの(”e\u0301”)として表現されます。あり、同等に扱われるべきです。これら2つの表現の等価性を調べるために1バイトごとに比較していたのでは正しい結果は導けません。同じ正規形に正規化される場合に、そのバイト表現が同じになるような正規形のセットを定義しています。Unicodeでは同じ文字を表すけれども見た目が異なる表現を同等とみなす「互換等価」を定義しています。この形式では等価です。これら2つ等価な形式に対して、Unicodeでは合成と分解を定義しています。合成は、結合して1つのルーンにできる複数のルーンをその1つのルーンにい置きv換えることです。分解は、ルーンを要素に切り離すことを指します。すべてNFから始まる次の表は、Unicodeコンソーシアムが各形式を識別する際に使っているものです。NFKDGoの正規化に対するアプローチ文字列に関する記事で言及したように、Goでは文字列内の文字が正規化されていることを保証していません。パッケージがそれを補填してくれます。これは正規化されていない文字列でも正しく動作します。正規化が必要でしょう。正規化のコストはタダではないですが速いです。NFDになる場合は顕著です。実際に、99.98%のウェブ上のHTMLページのコンテンツはNFC形式です。(マークアップは含めていません。含めた場合はその割合はより大きくなります。)ほぼ間違いなくたいていのNFCは(メモリの確保を必要とする)並び替えの必要なく分解するだけでNFDになります。並び替えが必要な場合の検出も効率的なので、それが必要なまれな区画に対してだけ並び替えを行うことで時間を節約することが出来ます。パッケージを使います。並び替えと正規化をインタリーブすることで、パフォーマンスに影響をあたえることなくその場で実行することが可能になります。オンザフライでの正規化のコストは事前に事前に文字列を正規化する必要がないことで補償され、編集時には正規化形式になっていることを保証します。2つのNFCで正規化された文字を合成してもNFCになるとは限らないからです。あることですが、事前に文字列が正規化済みであることがわかっているのであれば、明白なオーバーヘッドは避けるべきでもあります。何が困るのかこれまで正規化をできれば避けたいという話をしてきましたが、そもそもなぜそれが懸念事項になるのか疑問の人もいるでしょう。その理由は、正規化が必要で、正規化が何か、そして正規化を正しくする方法を理解することが重要である場合があるからです。これらについて議論する前に、まず「文字」という概念を明確にしなければなりません。文字とは何か文字列に関する記事で説明したとおり、文字は複数のルーンに渡ることがあります。この2つのルーンをまとめて1つの文字を表しています。文字の定義は実装によって変わります。(通常はアクセントなどの)修飾などを行う後続ルーンによるルーン列として定義しています。後続ルーンの列は空になりえます。正規化アルゴリズムは一度に1文字を処理します。理論上は1つのUnicode文字を作る上でのルーン数には上限はありません。3つアキュートが付いたものを見たことがありますか。これは、標準上は完全に正しい4ルーンの文字です。結果として、最下層においても、文字列は無制限のチャンクサイズの積み重ねの中で処理が行われる必要があります。インターフェースのように、文字列処理をストリームで取り組むときに扱いづらくなります。このモデルでは潜在的に無制限のサイズを保持するための中間バッファーも持つ必要もあるからです。実際に適用する場合には、このような大きな修飾のシーケンスを意味のある形で解釈することはありません。format(ストリーム安全な文字列形式)を定義しています。これは修飾(後続ルーン)の数の上限を最大で30に定めていて、この数は実用では十分な大きさです。U+034F)に置き換えられます。この決定によって、原文との一致性は少し下がりますが、より安全に処理できるようになります。正規化形式で書くGoのコード内で正規化をする必要がない場合でもなお、外部とやり取りするときにはそうしたくなる場合があるでしょう。NFCに正規化すると文字列を小さくでき、送信するコストを小さくすることが出来ます。ある言語、たとえば韓国語では、データを小さくすることは有用です。外部のAPIが特定の正規化形式を期待している場合もあります。外部のシステムと同様に、ただ正規化してNFC形式にしたい場合もあるでしょう。io.Writerをラップして使うのが良いでしょう。通常と同様にwriteする短い文字列を手早く変換したい場合は、この簡単な形式でも良いでしょう。パッケージは文字列の正規化のために他にも様々なメソッドを用意しています。必要に応じて最適なものを選んでください。“\u2126”)の違いがわかりますか。根本的には同じ文字の異形での細かな差異は、見逃しやすいものです。セキュリティの危険性を晒すような場所で用いることを禁止するのは良い考えです。NFKCやNFKDというような標準系は見た目が近い同一の形式を一つの値に対応させます。2つのシンボルの見た目が似ていても、実際に異なるアルファベットの場合はこのような対応はしないことに注意してください。して、これらの標準系で定義されたように異なる文字です。パッケージは文字列を修正する必要があるときにも助けになってくれます。置換したい状況を考えてみましょう。コードスニペットは次のようになります。含む文字列を考えてみましょう。後に挿入されますが、アキュートの前に挿入されてしまいます。これは期待した結果ではありません。このコードが複数のルーンを使った文字の境界を反映せずに、文字の真ん中にルーンを挿入してしまうことが問題です。パッケージを用いて、先ほどのスニペットを次のように書き換えることが出来ます。s[p:]}fmt.Println(s)この例は作為的なものですが、このコード片がやろうとしていることは明らかでしょう。文字は複数のルーンから構成されうるという事実を意識しましょう。避けることが出来ます。パッケージより提供されている、文字列の境界を扱う上で便利な機能にはイテレータがあります。これは選択した正規化形式での文字を1つずつイテレーションしていきます。技を披露する先にも述べたように、たいていの文字列はNFC形式で、このとき可能な限り土台の文字と修飾子は合成されて1つのルーンにされます。文字を解析する場合には、しばしばルーンを最小限の要素に分解した後のほうが扱いやすいことがあります。このようなときNFD形式が便利です。内のアクセントを取り除くために使うことが出来ます。先に述べたように、パッケージによっては正規化を事前に計算してテーブルに保存し、ランタイムでの正規化を必要最低限にします。型はこれらのパッケージに必要なルーン毎の情報にアクセスできるようにしています。Class)と分解情報にアクセスできる点です。パフォーマンスと比較してみましょう。次の表の1行目はすべて小文字でNFCになっており、すべての文字はそのまま返されます。2行目のサンプルは大文字も混じりNFCでない文字も含まれていて、変換が必要になります。ns)イテレータの結果に関する列ではイテレータの初期化をすでにしている場合としていない場合の両方の計測結果を表示しています。初期化をした場合は次回以降のイテレーションではバッファを再利用することが出来ます。ごらんの通り、文字列が正規化されているかどうかは非常に効率的に判断されています。2行目の正規化のコストはバッファの初期化によるものが大きく、そのコストは大きな文字列を扱う際にはならされます。実装を変更するかもしれません。パッケージを使う必要ありません。したい場合には便利です。パッケージについても簡単に触れまいた。この記事で理解したものの数よりも多くの疑問が湧いてきことでしょう。この話題に関する議論はまたの機会にしましょう。(translation)。