In article <E13tNkT-00030l-00 / ev.netlab.zetabits.co.jp>,
  matz / zetabits.com (Yukihiro Matsumoto) writes:

> じゃあ、なんで文字オブジェクトでなく整数(コードポイント)に固
> 執するのかというと、やはり互換性とメリットが見えないからなん
> ですね。というわけで、[ruby-dev:11427]のような説得メールはむ
> しろ歓迎します。

まず、私の文字オブジェクトに対するイメージですが、これは各種文字列に対
応して存在するものです。つまり、ASCII の文字列が String::ASCII クラス
であれば、String::ASCII::Character が ASCII 文字のクラスです。同様に、

String::ASCII::Character
String::ISO_8859_1::Character
String::ISO_8859_2::Character
String::EUC_JP::Character
String::Shift_JIS::Character
String::Big5::Character
String::EUC_KR::Character
String::UTF_8::Character
String::Byte::Character
String::UTF_2000::Character
etc.

というかんじでいろいろなクラスがあると想定しています。

このような構造から導かれる直接的なメリットは、ある文字がどの文字コード
に属するものかを文字自身が覚えておいてくれるということです。

結局、構造的にいえば、文字列の種類とその中でのコードポイントの対と等価
です。それにもかかわらずわざわざ文字クラスを導入することを勧める理由は、
いくつかあります。

* 文字の取り違えが起きない。

  たとえば、String::ASCII に String::EUC_JP 由来の平仮名を挿入しようと
  した時に例外を発生させることができます。

* 文字コードが隠蔽される。

  情報隠蔽です。文字コードという非本質的なものはなるべく見えにくいにこ
  したことはありません。とくに、今回は、文字コードを処理するモジュール
  の都合でかなり恣意的に 31bit 整数に表現した結果ですから、その表現を
  将来的に変えたくなった時に、Fixnum を生で扱うことはその変更を邪魔し
  かねません。

  たとえば EUC-JP の場合でいえば、94*94 をつめるかどうかを変えたくなる
  とか、補助漢字と JIS X 0201 片仮名の順番を入れ換えたくなるとか、最初
  に補助漢字を入れるのを忘れてたとか、JIS X 0201 片仮名を入れるかどう
  かに関する決断が変化したとか、どーせ round trip するんだからやはり
  UCS にしてしまおうと思い直すとか...

  たとえば Shift_JIS の場合でいえば、拡張領域の扱いを変えたくなるとか、
  JIS X 0201 片仮名をどの範囲に割り当てるかとか、JIS X 0208 をどうやっ
  て整数にエンコードするかとか、YEN SIGN と OVERLINE をどうするかとか...

  たとえば ISO-2022-JP の場合でいえば、JIS X 0208 1978 と 1983 を
  unify するかどうかとか、ASCII と JIS X 0201 Latin を unify するかど
  うかとか、unify しないとすればそれはどのような順番で整数にエンコード
  するのかとか、unify するとすれば、どのような関係で unify するのかと
  か、突然やっぱり現実を見据えて JIS X 0201 片仮名も入れようという結論
  に達するとか...

  最初からすべての選択を正しく行なえるなら Fixnum を露出させていても大
  きな問題はないでしょうが。

* polymorphism を実現できる。

  XString.method(codepoint, args) のかわりに
  XString::Character#method(args) を作る利点は、複数種類の文字クラス間
  の polymorphism です。

  複数のクラス間である程度共通な method があるなら、polymorphism を提
  供するのが OOP ってものです。

  たとえば UCS との変換で、character -> UCS については
  XString.ucs(char) と書くかわりに char.ucs ですむ、というわけです。し
  たがって、XString というクラスを明示的に記述しなくて済むので、ucs と
  いうメソッドがあればどの種類の文字でも動くプログラムが書けるというわ
  けです。

  なお、べつに UCS を特別扱いするつもりはありません。そういう method
  を提供できない(したくない)場合には提供しなければいいだけです。むろん、
  ucs という method を要求するプログラムにはそのような文字は使えなくな
  るわけですが。

  まぁ、char.convert(character_set) という形式のほうがいいかも知れない
  というのはたしかにそうかもしれません。この形式にももちろん文字クラス
  は必要ですが。

  他の例をあげるとすれば、文字自身にそれを表現可能な MIME charset を尋
  ねる、とか。

* 付加的な情報を保持できる。

  たとえば、動的にフォントをロードする機構をつけるとか、文字鏡だの、超
  漢字だの、Unicode Consortium のテーブルだの、UTF-2000 のデータベース
  だのにアクセスしてキャッシュするとか。ちなみに、UTF-2000 ってのは表
  現形式としては結局「文字は alist だ」というだけの話なので、その
  alist を XEmacs UTF-2000 以外で参照することは別に難しくないはずです。
  原理的には。

* 文字列の種類と文字コードの対を Array などを使ってアプリケーションが
  自力で管理するよりはずっとましな性能が得られる気がする。

  flyweight パターンを使うとか。

結局、文字をオブジェクトにする利点は OOP の利点そのものです。情報隠蔽
により実装の変更がやりやすくなり、実装が異なる文字でも同じような操作で
扱え、文字を拡張することが容易となり...

したがって、逆にいえば、はじめから完璧な設計が可能で実装の変更が行なわ
れず、一つのプログラムは一種類の文字(の実装)しか扱わず、文字を拡張する
ことは考えない、と仮定するならオブジェクトにしなくても困らないのでしょ
う。

もちろん、文字をオブジェクトにしなくてもプログラムは書けます。言語が
OOP を支援してくれなくてもプログラムは書けるのと同様です。情報隠蔽が行
なわれておらず、コードポイントの Fixnum が直接見えていても、その値に依
存しないプログラムは書けますし、複数種類の文字を扱いたければその種類を
示す処理モジュールと対にして扱えばいいわけです。

でもそれは悲しいんじゃないかな、と思うわけです。

> |* MIME な世界ではことごとく charset がついているので、どんな charset
> |  が指定されても(その charset をサポートするモジュールが用意されていれ
> |  ば、ないしは追加すれば)処理できるようなプログラムを書きたくなる。
> 
> これはできないといけないとおもいます。
> 
> が、その「処理」においての「文字オブジェクトの働き」は私には
> 自明じゃないです。ある文字コード系での「文字」が他の文字コー
> ド系において対応する文字があるとは限らない以上、あまり有効な
> 局面がありそうには思えないんです。

あるとは限らないこそ、必要な事項を文字に尋ねる機構が必要なんじゃないか
と思います。

解が必ず存在してそれを導く方法が一通りの問題をあつかうなら必要性は薄い
かもしれませんが、解は存在していないかも知れないけれど存在するならそれ
を得たい、という状況では、とくに。

たとえば、mail/news の引用を考えます。この場合、届いたメッセージと自分
で書いた部分をまとめることになります。この場合、まとめた結果を表現可能
な MIME charset が存在する保証は存在しないわけですが、それぞれを構成す
る個々の文字にその文字自身を表現可能な MIME charset を尋ねて、共通部分
をとりだせば、まとめた結果を表現可能な MIME charset を得られることにな
ります。むろん、結果が空になる可能性もありますし、複数出てきたらどうす
るかという問題には別途対処する必要がありますが。

encoded-word をデコードする時も同様です。encoded-word それぞれで MIME
charset は異なる可能性があるので、それらを連結するには同様の処理を行な
う必要があります。

複数の mail を並べて HTML に変換するのも同様です。それぞれの mail には
MIME charset が異なるものが入っているかも知れません。このようなものを
正しく扱えないと
  http://www.ruby-lang.org/cgi-bin/ruby-bugs-ja/1.6?id=25;user=guest
のようなことになります。HTML について深く考えると CCS が Unicode 固定
という話を考えないといけないかもしれませんが...

> |* HTML とかの文字参照を扱うには、(内部コードとして主に何を使っていよう
> |  が)UCS との変換が欲しくなる。
> 
> しかし、それは「任意の文字コード系のコードポイントからUCSの
> コードポイントへの対応表(関数)」という形で用意されるべきだと
> 思います。文字オブジェクトにその機能を持たせるなら、UCSを特
> 別扱いするというポリシーを導入することになりかねません(し、
> 任意の文字コード系の相互変換はそもそも不可能でしょう)。

char.ucs という形式は「任意の文字コード系のコードポイントからUCSのコー
ドポイントへの対応表(関数)」というものを呼び出す適切なインターフェース
だと思います。

まぁ、上でも書きましたが、char.ucs よりは char.convert(character_set)
のほうが UCS を特別扱いする感じがなくなっていいかもしれません。

任意の文字コード系の相互変換は不可能というのはそのとおりです。でも、可
能な変換をこのようなインターフェースで実現し、できない部分は例外を発生
するというのは悪くないと思います。任意の文字コード系の相互変換は不可能
だからといって、変換する手段を提供しないわけにはいきませんし。

> 「UCSのコードポイントへの対応表(関数)」を導入するのであれば、
> コードポイントを示す整数だけで十分なような気がします。

もちろん(OOP がなくてもプログラムができるのと同様に)可能です。

> 私自身に「文字集合やエンコーディングの変換は明示的に」という
> 前提があるのも、(なんとなく暗黙の変換を支援しそうな)文字オブ
> ジェクトに抵抗がある理由かもしれません。

polymorphism を信用しましょうよ...

> |* なんらかの autodetect 機能を入れることになるでしょうが、autodetect
> |  の結果のコード自体を直接処理するモジュールがあるんなら、そのまま扱え
> |  て何がいけない? なぜ EUC-JP とかに normalize する必要がある?
> |  (もちろん(stateful なのとか)変換せざるを得ないものもあるでしょうけど
> |  ね。)
> 
> これはちょっと理解できませんでした。あ、「おおむねひとつの文
> 字コード系で閉じているのが普通」ってことに対してですね。

後から読み返してみると意味不明ですね。少なくとも autodetect は関係ない
です。生のデータをそのまま扱えるモジュールがあるならなぜ正規化しなけれ
ばならないのか、という後半部分だけを意図していました。

> これはアプリケーション全体が「おおむねひとつの文字コード系で
> 閉じているのが普通」と考えているという意味ではなく(読み返す
> とそうとしか読めますが ^^;;;)、ただ、「ある文字列から取り出
> した文字に対する処理」はひとつの文字コード系に閉じているんだ
> ろうなと思います。

それはそうでしょうね。でも、その文字は Ruby によるプログラム内部に埋め
込まれた ASCII/EUC-JP/Shift_JIS/UTF-8/... の文字(ないしは文字列)と比較
されるかも知れないし、他の場所から入手した文字列に挿入されるかも知れま
せん。

> UTF-2000的アプローチをとらない以上、文字集
> 合と独立に存在できる「文字」はありえないので。

個人的な感想としては、複数の表現形式を同時に扱おうとした時点で、必然的
に多言語処理の問題にはまり始めていると思っています。

UTF-2000 のことをどう想像しているかはよくわかりませんが、あれは文字列
の表現形式としては単に文字が alist というだけの話です。文字列自身が複
数の形式をとり得るという Ruby の選択はある意味で UTF-2000 以上に一般的
で強力です。強力なだけに、抽象化を進めないと文字の扱いが破綻するのでは
ないかと想像しています。

> ある文字集合のある文字と別の文字集合のある文字が対応している
> と言う知識は、今回話している文字処理とは別の場所で与える必要
> があると思います。

そのインターフェースとして、文字クラスがいいんじゃないかなと、思うんで
すが。

例えば、文字の変換を
  String.character_convert(src, src_character_set, dst_character_set)
とするかわりに、
  src.convert(dst_character_set)
とする、と。

> 念のためですが、「そのまま扱える」ことは今回の開発の目標の一
> つです。

余談ですが、守岡さんも「そのまま扱える」ようなものを結構前から夢想して
おりました。それは ISO-2022-JP とかの stateful なものも含めて全部外部
コードを直接処理するというさらに強烈な話だったような気もしますが。まぁ、
statuful でも適当な間隔で state をcache しておけばそれなりな速度が出る
気がしますしね。

> autodetectも悩ましい点ですよねえ。

各文字コードごとにその文字コードとしてあるバイト列が正しいかどうかを判
定するルーチンを作るというのは誰でも最初に思いつくのですが、これだけで
はあまりうまく動きません。判定の結果、複数の文字コードとして正しいとい
う結果が返ってきた時に、どれを選んでいいかわからないからです。文字コー
ドの優先順位をつければいいじゃないか、と思われるかも知れませんが、これ
がうまくいきません。たとえば、Shift_JIS と EUC-JP の判定で Shift_JIS
の JIS X 0201 片仮名を禁止することが多いのは、許容してしまうとどちらと
しても正しいという結果がたくさんでてくるからです。この問題を解決する
heuristics はいろいろとあります。jless で使っているらしい Shift_JIS と
思っても 0xa4が1文字おきに出てきた場合には EUC_JP にするとというのや、
jvim で使っているらしいカウンタを使ったものや、最初だけ JIS X 0201 片
仮名を禁止して調べるとか、fj にながれた記事で統計をとってどの文字コー
ドで解釈した時に最もよくある内容になっているかを調べるとか...

結局、autodetect というのは、どの文字コードで解釈した時に想定した文字
列集合(例えば日本語の文字列)としてありがちなのかを調べるものだと思うの
で、そのために、文字種の統計をとるとか、面倒なら JIS X 0201 片仮名は滅
多に現れないと仮定するとか... まぁ、そのあたりで手を打てばいいんじゃな
いですかね。

> 「文字オブジェクトの必要性」を感じさせてくれる説得メールを待っ
> てます。;-)

しかし、まつもとさんに OOP の利点を説く日が来ようとは思いもしなかった
ですね。
-- 
[田中 哲][たなか あきら][Tanaka Akira]
「くっだらないコト聞いちゃったねー$(C⊇ ごっめーん$(C⊇」
  (魔法使い養成専門 マジックスター学院 2, 南澤ミヅキ)