はじめに
先の記事を書くに当たっていろいろ調べていたところ、「Ruby M17N の設計と実装」という記事を見つけた。そこで、今回はこれをネタに思うところを語りたい。ダシに使われたほうはいい迷惑かもしれないが、記事を公開すればそういうこともあるので、あきらめてほしい。
なお、この文章には用語の定義らしきものがいくつか出てくるが、いずれも厳密なものではない。ここでの議論に十分な程度のものなので承知されたい。
CSIとUCS Normalization
前掲記事ではCSI (Code Set Independence) とUCS Normalization(UCSはUniversal Charcter Setの略。かならずしもUnicodeとは限らない)とを対比して議論している。
(なお、前掲記事ではCode Set IndependenceではなくCode Set Independentを使っているが、形容詞と名詞を対比するのはおかしいので、ここではCode Set Independenceで統一する。)
CSIは特定の符号化文字集合(Coded Character Set (CCS)。長いので以下「文字セット」と略す)および文字エンコーディングスキーム(Character Encoding Scheme (CES)。以下「文字エンコーディング」)に依存しないプログラミングを指す。これは非常に美しく、また高い理想である。誰でも、特定の環境にべったり依存したポータビリティのないプログラムよりは、環境に依存しないものがいいと思うし、これは、それを文字表現について実現しようとするものだから。
いっぽうUCS Normalizationはその対極にある。特定の文字セット・文字エンコーディングを内部処理に決め打ちし、外部との入出力はデータの変換によって実現しようというものだからだ。これにより、同一方式をとる処理系間でのポータビリティが実現されるが、これはCode Set IndependenceならぬCode Set Dependence(CSD。いまここで作った用語で一般的なものではない)そのものである。CSIの理想主義の対極にある、ある意味、現実主義かも知れない。
なお、これらの概念は別に新しいものではない。ヨーロッパではISO/IEC 646の各国語版やISO/IEC 8859シリーズという複数の文字セットに対応する必要があった。また、各言語ごとに日付表記・ソート順が違っていたり、各国ごとに数値・通貨等の表記が異なっていたので、それにも対応が必要だった。単一市場でそれらに個別対応するのは難しかった。(だからCode Set Indepencdenceなのだ。ここでいうCode SetはCoded Character Setの略。MIMEでいうcharsetもCharacter Setの略なので、根源は同じ。)
いっぽう日本では、1980年代半ばにはISO-2022-JP、Shift_JISおよびEUC-JP(当時そんな用語はなかったし、厳密には違うところもあるけれど)が出揃っており、これらの文字エンコーディングに対応するためにUCS Normalization的アプローチをとったものもある。たとえばNemacs (Nihongo Emacs) は内部処理にEUC-JPを使い、ISO-2022-JPおよびShift_JISも入出力できた(なお、NemacsはのちにMule (Multilingual Emacs) に発展し、最終的にGNU Emacsに統合された)。
I18NとL10N
前掲記事では、L10Nについて:
L10N とは Localization の略で、地域化を意味しています。(cf. nls / national language support) 具体的には、それぞれの地域・言語に適したように変更することとなります。(後略)
と説明しており、その続きの文章を見ると、特定の地域・言語・文字エンコーディングその他にだけ、専用に対応することのように読める。また、I18Nについては:
I18N とは Internationalization の略で、国際化を意味しています。I18N とは、
- ソフトウェアのマルチバイト対応
- 各種メッセージや通貨記号等を地域ごとに容易に切り替えられる仕組みの整備
を行うことです。(後略)
と説明している。
しかし、上でいうL10Nはdirect localizationといって、I18Nとセットで語られるL10Nとは違うのである。(たしかに、大昔はそうやっていたというのは事実だけれど。Rubyなんて、21世紀にもなってそれに近いことをやっていたようなのだけれど。そういえば、JPerlってのもありましたねぇ。皆さん、面白いくらいに同じことをやっている。)
そもそもI18Nは、文字セット・文字エンコーディング・言語・地域その他に依存する情報・機能をモジュール化してプログラム本体から切り離し、それを状況に応じて動的に組み込むことで各種の言語・地域その他に対応できるようにする枠組みを作ることを指し、L10Nは、そのような枠組みのなかで組み込み可能なモジュールを作ることを指す。別にマルチバイト対応やメッセージ・通貨記号等の切り替えだけに特化した話ではない。
上記のような説明は極端な矮小化というべきだろう。
(まあ、ネットで検索してみるとそういう説明ばかり目につくのは事実だが。)
M17Nについては……コメントを差し控えさせていただきたいと思います。
CSIであることの困難
さて、CSIを実現するには、どこか下位レベルにCSDなモジュールが必要である。これは、たとえばOSがデバイスドライバーというデバイス依存部を下位レベルにもつことにより、ユーザープログラムにデバイス独立性を提供するのと同じである。
RubyではtranscodeおよびEncoding::Converterクラスがこの役目を果たしているように見える。これによって、外部の文字エンコーディングと、内部処理用の文字エンコーディングとの間に独立性が提供できる。これはPerlのEncodeモジュールに相当する。また、Rubyでは内部処理を文字エンコーディングに依存しないようにするためのさまざまな機能が提供されているようだ。
CSIのユーザープログラムが特定の文字エンコーディングに依存すべきではないのは、定義からして明らかだ。そして、それを実現するためには、ユーザープログラムはシステムから提供されるAPIを通じてのみ文字データを処理することが必要になる。
問題は、ユーザープログラムの要求を満たすだけのAPIが提供できるかどうかである。しかし、千差万別・多種多様の要求に対応できるAPIをあらかじめすべて用意しておくなんてことは、どう考えても無理である。
よって、ユーザープログラム側で自分の要求を満たす機能を作ることになる。
もちろん、そういう機能はモジュール化しておくことが望ましいし、汎用化されて標準提供モジュールになるかも知れないけれど、それらは必然的にCSDになってしまう。
美しいCSIの実現のためには、泥をかぶってくれるCSDなモジュールが必要なのだ……ユーザープログラムのレベルで。あれれ、CSIってどういう意味だったっけ。
ユーザープログラムのCSIを実現するために、それと同一レベルにCSDなモジュールが必要になってしまう。しかも、そういうモジュールは呼び出し元の文字エンコーディングに合わせて値のやり取りをする必要があり、内部的にはUCS Normalization的な構造になる可能性も高い。CSIのためのCSDに、CSIのためのUCS Normalization。やれやれ、まったく難儀なことである。
おわりに
あ、最後にいっておきますが、私は別にどちらがいいとかいってませんよ。UCS Normalizationは別の難儀を抱えているだけなんで。さらにいえば、Unicodeがいいともいってませんから。
Unicodeで処理を統一しても、それが使えなくて、別のものでやらざるを得なくなるかも知れないし。
16ビットUnicodeで統一したはずのJavaやWindowsは、Surrogate Pairが出てきて、Shift_JIS処理でバイト単位にやっていたのと同じことを16ビット単位でやっているし。Combining CharacterやらVariation Selectorやら、面倒くさいのはいっぱいあるし(Combining Character類似のものはISO/IEC 6937ってのがありましてねぇ、別にUnicodeの専売特許じゃありません。Backspaceで重ね打ちなんて古典芸能もありましたし)。ああ、Normalization Formってのもありましたね。ややっこしいったらありゃしない。どうせ、似たようなことはまた起こります。これが最後の一匹とは思えない。
まったく、世の中同じことの繰り返し。これが学園祭の前日なら、繰り返しも楽しかったかもしれないけれど。