世の中がほぼUnicode前提になってめでたしめでたし。とはいかなかった現実の話。

String型でできる文字列処理とか、ソースコード自体、特に識別子で使える文字とか。

軽くおさらい: Unicode

まあいろんなところでいろんな人が書いてると思うのでさらっと概要だけ。

Unicodeは、元々、「65,536文字あれば十分だろ」とかいう幻想の元、2バイト固定長の文字コードとして作られていました。 もちろん足りなくて、ビット数を拡張。基本が2バイトのままでこの拡張した分を取り扱えるようにしたのが今のUTF-16で、拡張分は2文字分(4バイト)を使って表現。 この、2文字分使って1文字を表すやつのことをサロゲートペア(surrogate pair: 代理対)と呼びます。

あと、ASCII文字も2バイトになるのを欧米人が嫌って、ASCII文字はASCIIコードのまま、逆に漢字・ひらがな・カタカナなんかは3バイトになるようなエンコード方式ができて、それがUTF-8。 固定長文字列とか幻想でした。サロゲートペアとか出てくるくらいならUTF-8でいいでしょう…

UTF-8やUTF-16へのエンコード前の状態、Unicode的に1文字として割り当てられているコードを、コードポイント(code point: 符号点、符号位置)と言います。 Unicodeコードポイントは今のところ21ビットの数値。

コードポイントこそが文字の最小単位かというとそうでもなくて、アクセント記号とか濁点とか、他の文字と組み合わせて使うことで、表示上は1文字に見えるけどもコードポイントは複数になる文字があり得ます。 「か」(U+304B)+「 ゙」(U+3099)で「が」1文字に見えたり、 「a」(U+0061)+「 ́」(U+0301)で「á」1文字に見えたり。

その他、漢字の異字体セレクターとか、絵文字の肌トーンとか、複数コードポイントで1文字になるものが結構あります。 こういう、表示上は1文字として扱うべきという単位を書記素クラスター(grapheme cluster)と言います。 UI的に、文字の選択や削除は書記素クラスター単位で行うのが好ましいです。 さもなくば、家族が1人1人消えていくホラーが発生するわけです

まとめ:

  • コードポイント: Unicode的に1文字として割り当てられてる21ビットの数値
  • エンコード
    • UTF-16: 2バイトですべての文字を表せると思っていた時代の名残。16ビットで収まる範囲はコードポイントそのまま、収まらない範囲は2文字分(4バイト)使って表現(サロゲートペア)
    • UTF-8: ASCII文字がASCIIコードのまま扱えるように、1~4バイトになるように符号化する方式
  • 書記素クラスター: 表示上1文字になって、選択や削除の際にはまとめて処理されるべき単位

プログラミング言語のString

Java (1995年リリース)の辺りから、新しめのプログラミング言語は大体Unicodeを前提としています。 ソースコードは、UTF-8で保存しておけば日本語とかも識別子(変数名とかクラス名とか)に使えます。 String型は標準でUnicodeを扱えたりします。

とはいえ、Unicode自体が紆余曲折あったので、プログラミング言語も紆余曲折振り回されます。

JavaやC#はString型がUTF-16だったりします。サロゲートペアの処理が結構面倒。 Unicodeがまだ2バイト固定長を夢見ていたころの世代の言語。 UTF-16文字からUnicodeコードポイントを得るためのAPIはあるんで何とかできはしますが。

Goなんかは文字列はUTF-8だそうです。

Swiftは、最近できただけあって、結構頑張っています。 内部的にUTF-8かUTF-16を問わず(String型が内部に弁別用のフラグをもってて、適宜デコードしてくれてる)。 API的にも、String型が、以下のようなメンバー(正確にはextension)を持っています。

  • characters: 書記素クラスターを列挙
  • unicodeScalars: Unicodeコードポイントを列挙
  • utf16: UTF-16エンコードした2バイトを列挙
  • utf8: UTF-8エンコードしたバイト列を列挙

まあ、ちゃんとしてるように見えて、Unicodeの仕様追加に対してちょっと危ういんですが…(後述)

プログラミング言語の識別子

一方で、ソースコード自体の話。特に、識別子に使える文字の話。

まともな神経をしてる言語は、Unicodeカテゴリーで識別子に使える文字を縛っています。 非常に大まかにいうと、たいていの言語で識別子に使えるのはLetterのみ。 記号は使えないように絞ってあります。 逆に、Letterなら大抵なんでも。日本語だろうが漢字だろうが使えます。

C# とサロゲートペア

ちなみに… 現状の C# はサロゲートペア使えません… 変数名に𩸽とか𠮷とかが使えない状態です。 つい最近まで仕様書に「Unicode Standard, Version 3.0」(サロゲートペアなんてものがなかった時代の仕様)って文字が入ってたくらいでして。 今は、「The Roslyn compilers depend on the underlying platform for their Unicode behavior」(プラットフォームによる。Unicode Standardの変更の影響を受ける)に仕様の文面も変更されることになってるんですが、サロゲートペアへの対応はまだ。

最近、中国人っぽい人から不具合報告を受けています。 C#チームの応答を見るに、修正入って使えるようになるかも。 まあ、仕様書的にサロゲートペアが使えてもおかしくなさそうな記述になってる今、 対応していないのはおかしいはずですんで。

Swiftの攻めっぷり

一方で、Swiftのやんちゃっぷりは凄くて… Swiftリリース時点でも、「絵文字が使える!」ってアピールされてたのは結構皆さん覚えてらっしゃるかと思えますが。 調べてみたら、絵文字どころかほんとに何でも使える。 個人的にツボったのは以下の2つのネタコード。

  • 0~9の和は1023 … 数字に見えますけど、「Mathematical Alphanumeric Symbols」って領域にある記号を使っています
  • 不可視識別子 … これも数式用の記号。変数xとyを、xyと並べたときに、それが何を意味するか(掛け算か、足し算か、関数適用か、添え字を並べただけか)を判別するために使います

バグでも実装ミスでもなく、仕様。 いいのか悪いのか… まあ、Letterに限定してもろくでもないコードは書けるんで、程度の問題なんですが。

Unicodeの変更/OSの対応状況に振り回される

Unicode、今もうバージョン9とかなんですっけ。基本は文字の追加だけなので、そんなに毎度毎度は問題を起こさないんですが…時々…

中黒のカテゴリー

有名な問題として、「・」(U+30FB、中黒)の扱いが変わったというやつがありまして。 中黒って、日本語的にはハイフンのような使い方をします。2つの単語を並べるときに区切り文字として使う。 ところが、間違えて、「ー」長音記号と同じカテゴリー(Pcカテゴリー)を割り当てられていたという過去があります。 これを、Unicode 4.1だか辺りで、ハイフン扱い(Poカテゴリー)に修正したそうです。

長音と同じであれば、たいていの言語で識別子に使えます。JavaやC#では、昔は中黒を識別子に使えていました。 それが、JavaはJava 7で、C#はC# 6で、いきなり識別子として使えなくなります。 破壊的変更に対してかなり保守的な言語であるJavaとC#が立て続けに破壊的変更を起こしたという。 言語仕様には全く変化がなく、ただ、使っているUnicodeのバージョンが上がったというのが原因になります。

絵文字の肌トーンと書記素クラスター

割かし最近の追加と言えば肌トーン。「絵文字とカラーフォントがパンドラの箱開けちゃった」と有名なあれ。

「なぜ白い肌の絵文字しかない」とかいう政治的な地雷を踏みぬいた結果、「Emoji Skin Tone Modifiers」という補助文字が作られて、 絵文字の後ろにくっつけると絵文字の肌色が変化するというもの。

iOS 8.3で対応したんでしたっけ。 それ以前は、肌トーンは「肌色の四角形」で描画されていました。顔文字と並べると、顔文字+四角形の2文字が表示されます。 以降は、ちゃんと1文字の顔文字で肌の色が変わります。

ここで、書記素クラスターの話に戻ります。 表示上1文字として描画されるべきものが書記素クラスター。 肌トーンはどうなるかというと、Unicodeコンソーシアムのレポートによれば「単一の書記素クラスターとして扱うべき」とは書かれています。 が、まあ、あくまでそれは、肌トーンに対応したOS上での話。 iOS 8.3より前なら2文字として表示されているわけだし、以降なら1文字扱いしてほしい。

これ、Swiftにとっては、characters(書記素クラスターを列挙)の結果が変わることになります。 現状は変更はされていなくて、肌色指定付きの顔文字のcharacters.countを調べると、2文字扱いされます。

そして、公式フォーラムでも問題に上がってるみたい。

議題は、要するに、

  • characters.countの数え方はプラットフォームに依るべきか
  • プラットフォームに依るべきとすると、プラットフォームごとにcharactersの挙動が変わることになるけど、それはありか
  • 挙動が変わるのを良しとしない場合、プラットフォーム依存な、ちゃんと表示されてる文字数を取る手段はあるか

という話。

もしかして、書記素クラスターは人類が手を出すにはまだ早すぎたのではないか…

サロゲートペア対応C#

何で今こんなこと調べてたかというと、ちょっと、C#コンパイラーいじってみるのの題材として、C#のサロゲートペア対応とかやろうかと思ってまして。

.NET Fringe Japanで「言語機能の作り方」って感じの話をする予定で、その一環。 実際のセッション内容としては、「うかつに独自機能とか考えるんじゃないぞ」という予防接種が半分、「それでもコンパイラーに手を入れたいっていうならデモくらい見せるけど…」みたいな内容が半分の予定で、この後半の内容になります。

どうせやるなら、多少芽のある話をやろうかと思った結果がサロゲートペア対応。 非ASCII文字の話は、日本人か中国人が率先してやらないと全然実装されないですからねぇ。

で、久々にUnicodeの状況とか、C#以外も含めたプログラミング言語の仕様とかを漁ってみてた結果が本日のブログ。 「絵文字はともかく、𩸽くらいは使えてしかるべきよなぁ」とか考えてたら、 ちょうど同じタイミングでRoslynリポジトリ内でその話題(ただし中国語)が出てたり。 Swiftがどこまで緩いの調べてたら想像をはるかに超えてやんちゃだったり。