Ruby 1.9で注目の新機構、M17N(多言語化)を、まつもとさん、卜部さんのお二方が語ります。M17Nとは何か、どんなメリットがあるのか、M17Nが実装されたことで開かれる可能性とは?
Ruby 1.9では文字列の抽象度が上がった
Ruby 1.8ではすべてがバイト列だったんです。で、Ruby 1.9ではコードポイント[1]と対になっている文字を表現するようになりました。ちょっと抽象度があがりました。
ですから、これからは"ABCあいう"という文字列の0番目を取ると「A」という文字を返します。で、3文字目から1文字を取ると、変なバイトではなくて「あ」という文字を返します。
― 3番目というのはゼロから数えて3番目のことですね。
ゼロから...皆さん分かりますよね。数字はゼロから数えますよね。
エンコーディングが使えるようになったので、マルチバイト文字を使う時にはエンコーディングプラグマ、僕らはマジックコメントと呼んでいますけれど、「# -*- coding: utf-8 -*-
」と指定してやらないとエラーが出ることがあります。この「-*-
」という記号はEmacsがファイルの文字コードが何かを調べる時に使う記号なので、これが付いているとEmacsにも分かるし、Rubyにも分かるという、一石二鳥になっています。
― Rubyについてくるruby-mode.elをEmacsにインストールしていただくと、勝手につきますので安心です。
ruby-mode.elは勝手なことするというか頭がいいというか、UTF-8でファイルを書いてsaveすると、突然エンコーディング行が増えるんです。あれ、こんなのあったっけ?と。
― あれ、まつもとさんが書いたんじゃないんですか。
僕は書いてないよ。誰が書いたんだろうね(笑)[2]。
それでEmacsはこうやってコーディングを理解してくれるのだけど、vimは理解してくれなくて「fileencoding=utf-8
」って書くんです。それでRubyの方はAdHocに「coding
」の後に「[:
または=
]<エンコーディング名>」っていう並びがあるとエンコーディング名だと認識してくれます。最短だと「coding
」だし「transcoding
」とか全然違うこと書いても認識してくれます。馬鹿なんだか頭がいいんだかよく分からないんですが、
― よきに計らってくれるRubyってことですね。
頑張ってます。ただ、これはRubyが本家ってわけではなくて、Pythonでも同じことしてるんで。
― Pythonっぽく書けば中身はともかくエンコーディングは分かってくれます。
これはソースの話ですが、ソースでないデータファイルをオープンするときに、エンコーディングを指定することもできます。
1 コードポイント:ある文字コードが、個々の文字に対して定義する数値のこと。 http://www.m17n.org/common/m17n-docs-ja/group__m17nCharset.html
2 「たぶん、中田さんです」(まつもとさん談)
M17Nの過去と現在
M17Nのコードを一番最初に書いたのは、2001年のお正月休み。2000年の時点でM17Nをやりたいという話はあったのだけれど、本当にやったのはその時期くらいで、実に8年越しくらいの機能です。
M17Nってのは「Multilingualization」の略で、MとNの間に17文字あるからM17Nというんですね。そういうのが海外では結構あるみたいで、I18N(Internationalization)とかL10N(Localization)とか言うらしいんですけど、M17Nって一言でいうと「多言語化」ってことです。「多言語化」って言っても色々あって、沢山の言語を同時に使えるような枠組みを作る。例えば日本語と中国語とインド語って、インドにはたくさん言語があるんですけど、そういう沢山の言葉を使えるようなもの。
文字ってものは、だいたいの場合エンコーディングに従って表現されます。つまり文字集合があって、どんな文字がありますっていうレパートリとしての文字集合があって、その文字集合に番号を振ります。その番号の並びをどうやってバイト列に置くのかと。
― (コンピュータの)プログラムなので、当然番号を振るという話になります。字は字として形があるんですけれど、それだけだと使えないので番号を振りましょうという感じで、一番から順番に、まあ穴があいていることもありますけど。で、そういった番号の付け方が一通りだったらよかったんですけど、世界中に何通りでは済まないほどありまして。
たとえばShift-JISとEUCとUTF-8とJISとかいろいろあって、その指定を間違えて文字化けしたっていうのは皆さんもよく経験があると思いますけれど。たぶん日本はね、一番文字化けの多い国だよね、絶対。
― そうですね。インターネットとかでは日本が一番文字化けしてそう。
そんな気がしますね。もともとASCIIは、全ての文字を0から127までの番号に割り振っていて、アルファベットくらいは書けますというものでした[3]。残りの128から255までに、追加の文字を入れましょうというのが80年代くらいまで。年齢が上の人は覚えているかもしれないけど、アルファベットとカタカナしかなかった時期があった。
それから登場したのがJIS X 0208[4]。世界最初のマルチバイト文字集合。実はJISが世界最初のマルチバイト文字だったんです。
― 日本人には明らかに文字が足りないのが分かるので。中国とかもやってそうですけどね。
当初、中国は漢字の表現をあきらめていました。ところがJISがやったのを見て「お、出来るじゃん」って決めたのがGBとかだったみたいです。[5]
― 日本人、あきらめが悪い
どうしてもカタカナだけでは我慢できない。それで文字集合を決めたのはいいんだけど、それをどう表現するか。ISO 2022っていう表現は、文字を区点っていう、何区何点っていう、2次元の表で表現する。「ここから先のバイトは区点を表しますよ」って方法だったんだけど[6]、ムチャ扱いにくくって、文字列処理はできないよね。例えば「AB」っていう文字を検索しようと思ったら、もちろんASCIIの範囲には当たるけど、もしかしたら全然関係ない漢字にも当たるかもしれない。
― 番号が関係なくつながっている感じ
「ここまでは英語ここからは日本語」って切り替えていくわけだから状態がある。それで、もうちょっと使いやすい方法が欲しいよね、と出て来たのがEUC-JPとかShift-JISで。
― EUC-JPとShift-JISも、1.8では一応壊れない程度には動いていた。
はい。そうなんですよ。僕日本人なので、一番最初にRubyを実装したときには、EUCとShift-JISは使えるようにしようと作っていた。それがある日、1997年だか98だったか、突然吉田さんという方から「このパッチ使えばUnicode使えますから」と。
当時Unicodeってものがあることはもちろん知ってたんですけれど、今のShift-JISとEUCに対応したRubyにUnicodeを対応させるのは面倒くさいなあと思っていたら「できましたから」って。
― 割と早いですよね。
詳しい時期は調べないと分からないんですけど、割と早い方でしたね[7]。
それで試したら本当に動いて、「バンザイ」ということで取りこまれて、それ以来、Shift-JISとEUCとUTF-8には対応していますという感じで来てました。それで2000年ごろに「そうは言っても日本だけのRubyじゃなくなってきてるから」っていうことで始まったのがこのM17Nプロジェクトだったんです。けれど互換性の問題もあって、なかなか(Rubyのメインツリーに)とりこまれず、2005、6、7年くらいまでずっと放置されていたんです。
本家のブランチはどんどん新しくなっていくのに、M17Nブランチのベースはずっとそのままで、バグもいっぱいあるし、使えねーって感じだったんだけど、「もうグジグジしてないで入れろ」と脅されて、最初のRuby会議の頃に、後に1.9になるバージョンに突っ込んで、開発して本家に取り込まれるという決意表明を(しました)。
3 ASCII:0から127まで数字に、欧米でつかわれる文字を割り当てている文字コード。現在の「ANSI INCITS 4」という規格です。 http://ja.wikipedia.org/wiki/ASCII
4 JIS X 0208:1978年に初めて制定された文字コードで、当初は「JIS C 6226-1978」という名称。これまで3度改正されており、現在の規格が「JIS X 0208:1997」 http://ja.wikipedia.org/wiki/JIS_X_0208
5 GB 2312やGB 18030など簡体字中国語の文字コード。
6 データ内にASCIIの文字集合と区点による文字集合の切り替えを行うコード(エスケープシーケンス)が埋め込まれます。例えば「まつもとゆきひろ『プログラミング言語Ruby』を大いに語る」という文字列には、先頭、「Ruby」の前と後、末尾のそれぞれにエスケープシーケンスが埋め込まれます。
7 実際には1999年1月でした。
コードセット独立
という訳で、ようやくたくさんのエンコーディングが自然に使えるようになりました。こないだ数えたら83使うことができる。聞いたこともないようなものも沢山あります。ISO 2022はもちろん、ISO 8859っていうヨーロッパでつかわれているシリーズは1から12、16くらいまであって。12番が欠番だったのかな?16番とかユーロサインが入ってたりして[8]。
― もちろん、アジアの方でも中国とか、
GB 18030というエンコードがあって、ユニコードのスーパーセットというのを決めてますし、台湾ではBig5、韓国はeuc-krとか。もちろんアジアの国でもユニコード化は急速に進んでいますけれど、レガシーなデータは消せないので、そういうのを変換対象として扱えるメリットはなかなかなくならない。
― RubyのM17Nの特徴は、別のコードに変換するのではなくてそのまま扱うというところで、これが茨の道なんですが。
たとえばUnicode(UTF-16)のデータが大量にあるとして、それを文字として処理したいとき、いったんUTF-8に変換して処理をして、またUTF-16に書き戻すという処理をするのと、これを変換しないで処理するのでは、テキスト処理の時間が倍くらい違うこともあるんです。当然と言えば当然ですけれど。この倍の速度が許容できないケースがそれなりにありまして。
各文字列は何というエンコーディングで書いてあるよ、という情報を各文字列が知っているんですね。そこで基本的に変換なしで処理することができる。これはShift-JISのファイルだよという所から読み込んできたらShift-JISとしてデータを読みだしてきて、それをShift-JISとして処理し、Shift-JISとして書き出すというプログラムの書き方ができます。
ただし、「このプログラムはShift-JISのデータも読み込みたいし、EUCのデータも読み込みたいし、Unicodeのデータも読み込みたいし、それをいろいろ切り替えて、切り刻んで処理したい」という場合には、最小公倍数みたいな文字列集合を作って処理しないといけないので、それは凄い大変です。現実的ではないので、そういうケースは仕方ないからUnicode使ってください。
― Unicodeより広い文字集合は、今のところあまり入ってませんね。Emacs Muleなんかも対応してますが。
Rubyのライバルとなるようなプログラミング言語、例えばPerlとかPythonとかPHPとかでは、内部ではUnicodeにという動きが主流です。JavaではUCS2の頃からそうですね。そうすると読み込んでくるときにUTFなりUCSに変換して、処理をして書き出すというモデルになります。
問題は幾つかあって、遅いという話は先ほども出ましたよね。もう一つは、ラウンドトリップ問題。文字コードって歴史的事情のカタマリで、でたとえば日本ではよく円マークが出てくるけれど、海外ではバックスラッシュだったっていう問題がありますけど、Unicodeの中には円マークに対応する記号とバックスラッシュに対応する記号が両方あるわけです。そうすると、あるテキストの円マークをUnicodeにする時に、バックスラッシュに変換すべきなのか円マークに変換すべきなのか。例えば200の前に円マークがあるときには、円マークにしないといけないような気がしますね。だからといって、すべての円マークに見えるものを円マークに変換しちゃうと、プログラム中の「\n
」が、「\n」となって、プログラムはそれをメタキャラクタとしてみてくれないかもしれない。
― 人間の目からみる時には勝手に処理している訳ですけれど、プログラムから見ると違う字ですよという話ですね。
どうしようかという話が起きるわけですね。そういう文字が円マークだけでなく複数あるんです。「~」と「~
」とか「|」と「¦」というのが、普通に使う範囲でもいっぱいあるわけです。こういう風に、変換っていう作業は文字化けの原因になるので、実はできるだけ避けたい。なので、Rubyでは出来るだけ避けるように作られているわけです。
もう一つは、例えば世の中には「文字鏡」[9]とか「TRONコード」[10]っていうエンコードがあって、例えば古典の研究者にはUnicodeは十分ではないらしいです。
― 古典の時代にはもちろん文字コード、文字に番号が付くなんてことは考えられてなくて、文字にいろんな形があって、それぞれの文字が同じ文字なのに違う形になってたりとか。
あとトンパ文字ってありますよね[11]、あれはまだUnicodeが振られていないんで。
― 世の中にはまだUnicodeの割り当てられてない文字がまだ存在するらしいです。徐々に振られているらしいですけど。
研究的には別の文字が割り振られなくてはいけないんだけど、Unicodeでは一緒の文字が割り当てられちゃってるケースがたくさんあるんですね。あと、そもそも文字が割り当てられてないとか、そういうのを扱おうとすると、Unicodeよりもっと大きな文字集合を扱わないといけないケースがたまにある。
文章の研究って文字列処理の塊みたいなもんなので、そういう人たちも本当はスクリプト言語を使ってガンガン処理したいんです。「この単語が何回出てくるか数えたい」とか。でもUnicode中心のプログラミング言語だと、Unicodeで処理できる範囲の研究しかできない。
そこでRubyですけど、まだそこまで行ってませんが、フレームワークは用意しています。文字鏡を扱うようなフレームワークは200行もあれば書けるので、文字鏡を扱うようなライブラリを書いてくださいと。それを組み込めばUnicodeよりずっと広い、研究者が必要とする文字を扱えるようになる可能性があるわけです。まだできていないですけれど。
― 今のところまだできていないですけれど、将来的にそういうことができるようになる可能性があるわけですね。誰かが書いてくれれば。
実際にですね、僕が昔、M17N版をリリースしてたわけですけど、それを使って研究してたひとがいるらしいですよ。本当にいるんだってびっくりしたんですけどね、「使ってるのか」って。
― チャレンジャーですね。
ですね。需要はあるんですね。人類数十億のうちで、直接恩恵を得る人は100人もいないと思うんですけど、だけど可能性を閉じないということがですね。
― Rubyをここにおられる皆さんのように普通に、EUCやJISで使っている分には「何が変わったの?」という感じがするかもしれませんけれども、実は可能性という点では凄いところに広がっていくという感じですね。
Rubyのこういうやり方をCSI(Code Set Indipendent)と呼ぶんですけど、誰もやったことなかったんです。プログラミング言語として。なぜかというと難しいから。誰もできると思ってなかった。
― 今回のRubyのM17Nの実装について、誰もが不安だったのが「本当にできるのか?」って言う点で。誰もそこまでやったことがない。
ただ幸い日本人は、国内に複数のエンコーディングがあって、日常的に複数のエンコードを切り替えながら操作できないと話にならないという国に住んでいる。そういう技術がたまっていて、僕もプログラマ暦が職業プログラマとしてもそろそろ20年くらいになりますが、その中でも、海外のソフトを日本語通るようにするというような仕事もたくさんしているので、そういう経験と技術の蓄積があって、技術の蓄積があるので、もしかしてできるんじゃないのとやってみたらできちゃって。
世界に先駆けて、じゃないけど。
頑張ってやってきた割には「世界がUnicodeに統一されちゃって、めでたしめでたし」って言う話にならないとも限らないんですけれど。まあ、がんばったんだけど過去の遺物だよね、という言われ方をする可能性はゼロではない。ただ、まあ、それでも意味はあるんじゃないかなという気がします。
8 Ruby 1.9.1のソースコードを見ると登録されているものが分かります。 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_9_1/enc/
9 今昔文字鏡 :文字鏡研究会が開発する文字情報システム。収録文字数は約15万字で、これまでのコンピュータ上における漢字問題を解決する可能性を模索しています。
10 TRONコード:東京大学の坂村健博士によって提案されたコンピュータアーキテクチャ「 TRON 」のために策定されている文字コード。収録文字数に制限がないという特徴があります。
11 トンパ文字:中国のチベットや雲南省に住む少数民族に伝わる象形文字の一種。
(つづく)