はじめに
2007年のクリスマス(UTC)から始まったRuby1.9にはRuby M17Nが搭載されています。Ruby M17Nによって、Ruby1.9は世界中の文字を別々のエンコーディングで同時に扱えるようになりました。この記事ではそんなRuby M17Nを紹介します。
M17Nとは
そもそもM17NとはMultilingualizationの略で、多言語化という意味です。頭文字のMと末尾のNの間に17文字あるので、M17Nと略します。同様の略し方をする言葉には、国際化(Internationalization)を意味するI18N、地域化(Localization)を意味するL10Nなどがあります。この略し方はDEC起源で、元々Scherpenhuizenという名前の人のアカウント名を制限6文字以下で名付ける際に、管理者がS12Nと付けたことに由来するそう です。
Ruby1.8の状況
プログラミング言語で日本語が扱えること自体は珍しくなく、Ruby1.8でも不十分ながら$KCODEやjcode.rbという仕組みがありました。1.8では-Ksを指定することでShift_JISの円記号問題を回避したり、正規表現の誤マッチを防ぐことはできていました。
また、jcode.rbをrequireすれば、Stringが$KCODEで符号化されているとみなし、文字列としてのAPIを提供していました。しかし、あくまでStringはバイト列であるというのが基本でした。
他の言語環境
国際化を行う際、世のほとんどのプログラミング言語環境では内部コードをUnicodeにする方式(Unicode Normalization方式)を採用しました。つまり、Stringがバイト列であったものを、Unicodeコードポイント列(一部言語ではUTF-16 code unit列)としたのです。この方法は概念的にも実装的にもそれまでのバイト列中心のものから変化が比較的少なく、アメリカも含めて広く支持を獲得することとなりました。
Ruby M17Nのはじまり
まつもとさんによると、Ruby M17Nの始まりは吉田さんのUTF-8対応パッチ にあるそうです。当時のRubyもSJISとEUCには対応していましたが、さらにUTF-8にも対応することは不可能だと思われていました。しかし、吉田さんのパッチは現実的な実装でUTF-8対応を可能としていました。
ここからまつもとさんは、この仕組みを拡張すれば複数のエンコーディングを同時に1つのシステムで扱えるようにすることが可能なのではないかと思い立ったのです。その後いくつかの実験を経て、2006年頃からRuby1.9の目玉であるYARVの安定化と平行してM17Nの本格的な実装が始まりました。
Ruby M17Nの特徴
Ruby M17Nでは内部コードを統一せず、文字列それぞれに文字コードを持たせています(CSI方式;Code Set Independent) 。このため、複数のエンコーディングの文字列を同時に混在させることができます。また、外部コードをそのまま内部コードとして用いる事で、入出力時に文字列の変換を行う事なく、素早い処理が可能となっています。
Ruby M17N とは要するに何か
端的にはバイト列を文字列として扱うための仕組みと、それにまつわる諸々の仕様・機能のことです。バイト列にエンコーディングを付与すると、バイト列だったStringは文字列として動き出します。それゆえに、Ruby M17NではStringがバイト列でも文字列でもあるので、両者の区別を常に意識する必要があります。
Ruby M17Nの影響
Ruby M17Nに関連した変更には様々なものがありますが、主要なものを解説していきます。
StringがEnumerableでなくなった
Ruby1.9ではStringはEnumerableではありません。よって、String#each等は使えなくなりました(Ruby1.8では行ごとに繰り返された) 。なぜなら、Ruby1.9のStringは、バイト列でもあり、コードポイント列でもあり、文字列でもあり、行の列でもあります。よって、eachは削除され、それぞれに対応する、each_byte、each_codepoint、each_char、each_lineが用意されました。
String#[]が文字を返す
とは言っても、やはりStringは第一には文字列です。ゆえにString#[]は、与えられた文字位置に対応する文字を返します(Ruby1.8では与えられたバイト位置に対する数値を返した) 。これと関連して、文字リテラルは文字を返すようになりました。例えば、?aは"a"を返します(以前は"a"のコード値である97を返していた) 。
Stringにエンコーディングを教えよう
Stringがエンコーディングを持つとは言っても、そのエンコーディングは誰かが教えなければなりません。まず、ソースコード上のリテラルはmagic commentで教えます。例えば、ソースコードの冒頭にcoding:utf-8と書くと、そのファイルのリテラルはUTF-8であるとみなされます。
また、標準入力やファイルなどのIOから入ってきた文字列にもエンコーディングを教えます。デフォルトでは標準入力にはロケールのエンコーディングが設定されています。例えば、日本語WindowsではShift_JISの亜種であるWindows-31Jが、Unix系の環境では環境変数$LANGの値に応じて、例えばja_JP.UTF-8ならUTF-8に、ja_JP.eucJPならEUC-JPに、en_US.ISO8859-1ならISO-8859-1に設定されます。ファイルから読み込んだ文字列にエンコーディングを教える場合は、ファイルを開いたときに明示的にエンコーディングを指定します。例えば、open("foo.txt", "r:utf-8")などと指定できます。
強制的にエンコーディングを教えるにはString#force_encodingが使えます。ただ、これは上記の方法で指定できない場合のみにしないと、本質的な問題を解決できなくなってしまいます。
コード例 各種エンコーディングの指定方法
#/usr/local/bin/ruby
# -*- coding: utf-8 -*-
# リテラルは magic comment で指定
str = "いろは"
p str.encoding #=> #<Encoding:UTF-8>
open("foo.txt", "r:euc-jp") do |f|
p f.read.encoding #=> #<Encoding:EUC-JP>
end
str = ["82A282EB82CD"].pack("H*")
p str.encoding #=> #<Encoding:ASCII-8BIT>
str.force_encoding("Windows-31J")
p [str, str.encoding] #=> ["いろは", #<Encoding:Windows-31J>]
使ってみよう?
以上のことを念頭に置けば、いくら巨大な仕様のRuby M17Nと言えども、個別撃破が可能になります。基本的なところで無駄に悩むことがないように、Ruby M17N の設計と実装 を読んでから始めることを強くお勧めします。後は、新しいメソッドに出会ったとしてもるりま を見ればわかるはずです(載っていなかった場合や説明がわからなかった場合はruby-list等で相談するか、redmine で報告してください) 。
まとめ
Stringは文字の列(バイト列と区別しよう)
String にエンコーディングを教えよう(magic commentを書く)
リファレンスを見よう(足りなかったらredmine で報告)
次回は、Ruby M17Nをはじめとして方々で役に立つかも知れない、文字コードやエンコーディングの基礎知識をまとめます。