RubyKaigi 2025 キーノートレポート

Mari Imaizumiさん「Ruby Taught Me About Under the Hood」 〜RubyKaigi 2025 1日目キーノート

2025年4月16日から18日まで、愛媛県県民文化会館にてRubyKaigi 2025が開催されました。1日目は、ima1zumiさんことMari Imaizumiさんによる基調講演からスタートしました。タイトルは「Ruby Taught Me About Under the Hood」です発表スライド⁠。

今回の会場の特徴として、メインホールに毎日違う緞帳どんちょうが掛けられていました。1日目は、青く輝く波の緞帳です(第一緞帳「波響く」東山魁夷⁠⁠。

緞帳が上がると「おはようございます!」と笑顔のima1zumiさんが登場しました。

文字コード愛好家のima1zumiさん

ima1zumiさんは、IRBやRelineのメンテナとして活躍しています。RubyのUnicode 15.1.0対応をきっかけに、1ヶ月前にRubyコミッターになったことを報告すると、会場は大きな拍手の渦に包まれました。

「文字コード愛好家」を自称するima1zumiさんは、文字コードは奥深く「好き」かは分からないが「面白い」⁠興味深い」と表現します。

文字コードは「枯れた技術」⁠難しそう」と思われがちなテーマであり、Unicode時代の今、あえて学ぶ意味があるのか?と疑問に思う人もいるかもしれません。ima1zumiさんはそんな疑問に答えるように、コンピューターで文字を扱うことの難しさと文字コードの奥深く不思議な魅力について、自身の経験をもとに語ってくれました。

RubyKaigi愛好家でもある

RubyKaigi Takeout 2021(オンライン開催)を含めると、5回目の登壇となるima1zumiさん。文字コードやRelineに関するテーマを継続して発表してきました。

また、RubyKaigiへの登壇にとどまらず、有志とともにCosenseのRubyKaigi関連情報をまとめ、RubyKaigi 2024 感想記事まとめ の作成、RubyKaigi登壇者一覧サイトRubyKaigi Speakersの公開、さらには 𝕏のRubyKaigi関連投稿まとめなど、さまざまな形でRubyKaigiを盛り上げ、楽しみ続けています。

松山出身のRubyist、Rubyコミッター、そしてRubyKaigi愛好家として、オープニングキーノートを飾るにふさわしい存在です。

文字コードの歴史

キーノートではまず、「文字コードとは何か」というところから丁寧に説明が始まりました。

文字コードとは、コンピュータ上で文字を取り扱うために定義された、文字の集合とそれをビット列に変換する方法(文字符号化方式)を組み合わせた仕組みのこと。コンピュータにとって、文字はただのビット列にすぎません。

では、なぜ「文字コード」という仕組みが必要なのでしょうか? そのルーツを辿るため、ima1zumiさんは文字の符号化にまつわる歴史を電気のない時代まで遡って紹介していきました。

電気のない時代の情報伝達

電気もコンピューターもない時代、人々は旗や煙、太鼓といった物理的な手段で遠くまで情報を伝えていました。その代表例が狼煙のろしです。
煙の有無や色で合図を送るシンプルな仕組みですが、天候に左右されるという弱点がありました。

18世紀には、望遠鏡を使った光学式通信(セマフォ/腕木通信)が登場し、中継拠点をリレーして一文字ずつ符号化して伝えることで、数百キロ先にまでメッセージを届けられるようになりました。

電気通信時代の幕開け

電気の実用化によって登場したのがモールス符号です。
モールス符号は、ドット.やダッシュ-の組み合わせによって文字を符号化し、音や光の信号として送る仕組みです。受信側はそれを解釈し直すことで初めて「文字」として受け取ることができます。

この「文字をそのまま送るのではなく、符号化して送る」という考え方は、現代のデジタル通信にも基本的な仕組みとして使われているとima1zumiさんは語ります。

コンピューターと文字コードの誕生

コンピューターが登場すると、1963年にASCII(アスキー)、1964年にEBCDIC(エビシディック)が誕生します。ASCIIは、ASA(現在のANSI)によって制定された7ビットの文字コード体系で、パソコンやネットワーク通信の基礎として広く普及しました。EBCDICは、IBMが策定した8ビットの文字コード体系で、IBMメインフレームを中心に使用されました。
同じ文字でも文字コードが異なるとバイト列が大きく異なります(たとえば「A」は、ASCII(Hex)だと\x41、EBCDIC(Hex)だと\xC1になります⁠⁠。

文字コードの混乱期

1960年代以降、コンピュータが世界中に普及していく中で、各国や産業界ごとに独自の文字コードが策定されました。国際化対応や互換性の確保という課題に対して、それぞれが独自に対応していたため、異なる環境間のデータの受け渡しに混乱を招くことも少なくなかったと言います。

UnicodeとUTF-8の登場

その混乱を解決するために生まれたのがUnicodeです。世界中の文字を体系的に扱うための標準的な規格として、1991年に策定されました。

Unicodeでは、⁠U+0000」から「U+10FFFF」までのコードポイントを用いて、文字を一意に表現します。例えば、U+0061は「a⁠⁠、U+1F34Aは「🍊」を表します。

コードポイントを符号化する方法(文字符号化方式)にはいくつか種類があり、中でもUTF-8は、ASCII互換性を保ちながら1〜4バイトで表現できる可変長エンコーディング方式として、現在最も広く使われています。

ここまでの情報伝達の歴史を振り返ると、どの時代も「文字をどのように符号化し、取り扱うか」が課題だったことが分かります。情報を伝達するには、文字を数値やビット列に置き換える必要があり、それをいかに整備していくかが歴史上大きなテーマでした。
こうして様々な文字コードが誕生しましたが、各国の文字を適切に扱うには既存の文字コードでは限界があり、やがて世界的にUnicodeのような統一規格を作る流れへと進んでいきました。現在では、多くのシステムやアプリケーションがUnicodeを採用しています。

文字コードとの出会い

ima1zumiさんがプログラミングを始めたのは、2016年頃のこと。
Unicodeが主流の時代に、なぜ文字コードに強く興味を持つようになったのでしょうか?

Unicodeへの移行が進んだ一方で、依然として文字コード間の互換性の問題やUnicodeを正しく扱う難しさがあり、それらを扱う上で多くの課題と面白さがあることに気づいたと言います。現代においても文字コードの存在感は大きく、こうした体験を通じて文字コードに「のめりこんでいくことになってしまった」とima1zumiさんは語ります。

メインフレーム環境でのEBCDIC

ima1zumiさんが最初に出会った文字コードはEBCDICでした。
初めての仕事でメインフレームを扱うことになり、技術スタックはCOBOL、アセンブラ、JCL、z/OS、エディタはISPF EDIT、文字コードはEBCDICを使用していました。

EBCDICは8ビット長の文字コードで、256種類の文字を扱うことができますが、日本語のひらがな、カタカナ、常用漢字などの膨大な文字数を全て表現することは不可能でした。

そのため当時の開発現場では、コードコメントの多くが半角カタカナで記述されており、読みやすさを保つために半角スペースを入れるなどの工夫が欠かせませんでした。

さらに、漢字を扱うには エディタ上の「見えない文字」である制御文字(Shift-InやShift-Out)で入力モードを切り替える必要がありました。制御文字をうっかり削除してしまうと、表示が崩れたり文字化けしてしまうこともあり、細心の注意が必要だったと言います。こうした体験から入力した文字がそのまま表示されることは、当たり前ではないと気づきます。

家族の絵文字を入力するとIRBがクラッシュ

数年後、フィヨルドブートキャンプでRubyやRailsを学んでいたima1zumiさん。顧問の五十嵐さん(@igaiga555)から教えてもらった家族の絵文字(🧑‍🧑‍🧒‍🧒)をIRBに入力してみたところ、IRBがクラッシュしてしまいます。

不具合を報告したところ、Relineの作者である糸柳さん(@aycabta)「自分で直してみたら?」と声をかけられ、修正することにしました。

クラッシュの原因は「カーソル位置の計算」

IRBに家族の絵文字(🧑‍🧑‍🧒‍🧒)を入力した後、バックスペースを2回押した瞬間にIRBがクラッシュしてしまいます。その原因は、Reline内部でカーソル位置を計算する際にnilが混入してしまいエラーが発生する、というものでした。

家族の絵文字は見た目は1文字ですが、実際には7つのコードポイントから構成されています。4人の顔の絵文字とその間をつなぐZero Width Joiner (ZWJ) と呼ばれる制御文字がその構成要素です。

家族の絵文字の入力時は、構成文字それぞれの文字幅を計算しており、カーソル位置が右に大きくズレていました。削除時は、コードポイント単位ではなく見た目の文字単位(書記素クラスタ単位)で削除しており、カーソル位置と内部の文字の状態がズレてしまい、クラッシュしてしまっていました。

このように見た目は同じ1文字でも、コードポイントの数が異なる場合があります。⁠目で見たときの文字の単位」「実際のコードポイントの数」の乖離を解決するために、Unicodeでは ⁠Grapheme Cluster⁠(書記素クラスタ)という考え方があります。

書記素クラスタとは、見た目上は1文字でも複数のコードポイントからなる場合、それらをまとめて1文字として扱うための仕組みです。アクセント付きの文字や複合絵文字などを書記素クラスタとして扱うことで、見かけ通りの操作ができるようになります。

Ruby 3.0に修正内容がマージされる

ima1zumiさんは、入力時に「書記素クラスタ単位」でカーソル位置を計算するように修正することで不具合を解消し、その成果はRuby 3.0からRelineにマージされました。

この経験を通じて、書記素クラスタの仕組みやUnicodeの奥深さに触れ、Emojiの挙動についても調べるようになったとima1zumiさんは語ります。単なる文字の符号化を超え、見た目通りの挙動をしない文字があることに面白さを感じ、文字コードの奥深さにさらに引き込まれていきます。

RubyにおけるUnicodeのアップグレード対応

ima1zumiさんは、RubyWorld Conference 2024のハッカソンにて、RubyにおけるUnicodeのバージョン対応が止まっていることに気づきます。ハッカソンがきっかけとなり、RubyのUnicodeのアップグレード対応を始めることにしました。

どのようにアップグレードするのか

Unicodeは年に一度のペースで新バージョンがリリースされます。Rubyがこれに追従することで、多言語や絵文字などを含む最新の文字セットを正しく扱えるようになります。
では、実際にどのようにしてRubyに取り込んでいくのでしょうか?

UnicodeではUnicode Character Database(UCD)というテキストファイルにて、文字に関する情報やそれに付随するプロパティを定義しています。どのコードポイントはどんな文字になるか、その文字のカテゴリはなにか、といったことが一覧として整理されています。
RubyでUnicodeの新しいバージョンを取り込む際には、UCDをRuby内部にまとめて取り込みます。

具体的には、ビルドの設定でUnicodeのバージョン番号を更新した後、enc-unicode.rbというスクリプトを実行すると、Unicodeの仕様に合わせたヘッダファイルが自動生成され、それに対応するテスト群も自動で追加される仕組みになっているそうです。ima1zumiさんは「最新のUnicodeの仕様をRubyに正しく取り込めるように工夫されている」と紹介していました。

【Unicode 15.1.0】 Indic_Conjunct_Breakを扱えるようにする

実際のUnicode 15.1.0への対応プロセスについて見ていきましょう。

Unicode 15.1.0への対応のため、先ほど説明した通り、ヘッダファイルやテストを自動生成しようとしましたが、UCDのパースに失敗してしまいました。その原因は、Unicode 15.1.0から新たに追加されたIndic_Conjunct_Breakというプロパティです。

Indic_Conjunct_Breakは、インド系文字の合字を一つの書記素クラスタとして扱えるようにするルールです。たとえば、Devanagari文字で「क」「त」が合わさって「क्त(kta⁠⁠」のような合字になるケースがあり、これを正しく扱うにはIndic_Conjunct_Breakの情報が必要です。 この新しいプロパティをparserで扱えるように修正を加える必要があったと説明していました。

【Unicode 15.1.0】テストの失敗とOnigmoへの対応

無事、UCDの取り込みは成功したものの、次にぶつかったのはGraphemeBreakTest.txtから自動生成されたテストの失敗でした。具体的には、⁠क्त(kta⁠⁠」というような合字が分割されてしまい、テストが失敗してしまうというものでした。

ima1zumiさんは「これはRubyの正規表現エンジンである、Onigmoの書記素クラスタの分割の実装にて、Unicode 15.1.0の新しいルールに適用できていないのが原因です」と説明しました。

UnicodeにはGrapheme Break Rulesというルールが定義されており、制御文字や合字、絵文字などにおいて、どこで分割できるか、あるいはできないかを細かく定めています。

たとえば、家族の絵文字のような合成絵文字に関してはGB11ルールが適用され、分割されないようになっています。Unicode 15.1.0では、インド系の合字を正しく処理できるようにGB9cが追加されており、このルールを正規表現エンジンのOnigmo側に反映する必要があったと話しました。

ima1zumiさんは、Onigmoの実装について「Unicodeの仕様に正規表現のサンプルが書いてあったのですが、正規表現エンジン内部に正規表現を書くことはできないため、どのようにロジックを実装するか悩んだ」と話していましたが、最終的にはGB9c相当のNodeを追加することで対応し、無事テストも通過するようになったそうです。

実際のプルリクエストはこちらです。

コミッターになったima1zumiさん自身でこのプルリクエストをマージしたそうで、⁠これはなかなか感慨深いマージでした」と嬉しそうに語っていたのが印象的でした。

【Unicode 16.0.0】取り込むにあたっての課題

ima1zumiさんは、Unicodeの最新バージョンである16.0.0への対応については「UCDの取り込み自体は成功しているものの、正規化関連のテストで一部失敗している」と述べました。

Unicodeの正規化にはNFC、NFD、NFKC、NFKDという4つの形式があり、文字列の比較や検索で統一的に扱うために用いられます。

たとえば、éという文字は1文字としても表せますし、eとアキュート記号(´)の2文字としても表せます。こうした違いを吸収するのが正規化の目的です。正規化することで、検索時にユーザーの期待した結果を返すことができます。

現状のテスト失敗の原因として、次のことに触れていました。

  • 正規化の際に一度合成したあとにさらに合成が必要になるようなケースで、1回の合成処理しか行われていない可能性がある
  • ハッシュによるマッチ処理の誤りの可能性がある

ima1zumiさんは、⁠Rustの実装を参考にロジックを書き直しているが、Rubyの最適化を外したことでパフォーマンスが劣化している可能性があるので、今後はクイックチェックプロパティを活用できないか探っていきたい」と語っていました。

なお、ima1zumiさんからの後日談として「RubyコミッターのMartin先生(Martin J. Dürst氏)と一緒にRubyKaigi中に作業を進めて、Unicode 16.0.0をマージできた」という話を伺いました。RubyKaigiの会場内でRubyの開発が進んでいくことに感動したエピソードです。実装はプルリクエストを確認してください。

Ask the Speaker Share Your Thoughts

最後にima1zumiさんは、同じくスピーカーであるKanekoさんの発言を引用しながら、⁠物理で顔を合わせてコミュニケーションできるのがRubyKaigiの良さ。面白かった、良かったという声が励みになります。分からないことがあれば何でも聞いてください」と聴衆に呼びかけていました。

筆者もスピーカーに質問したり、感想を伝えることで新しい発見があったり、技術的な関心が増していくのを実感しています。積極的にスピーカーに声をかけていこうと思えた一幕でした。

深掘りの楽しさを伝える

発表の締めくくりでは、⁠たまたま文字コードと出会い、たまたまRubyと出会い、いろんな人に助けてもらいながら気になることを深掘りしていったら、ここまで来ていた」と自身の道のりを振り返りながら、⁠掘った先にはお宝があるかもしれないし、掘る過程自体が楽しい。あなたの好奇心を刺激する何かをぜひ見つけてください」と、参加者に向け温かい言葉を残しました。

ima1zumiさんの人柄があふれる、素晴らしいオープニングキーノートでした。

おすすめ記事

記事・ニュース一覧