サロゲートペア識別子
C#は、識別子に使える文字をUnicodeのカテゴリーで判別しているわけですが。「○○Letter」カテゴリーなものはおおむね識別子に使えます。
𩸽(ほっけ)
Letterなのに使わしてもらえないかわいそうな文字もあるわけですが。
𠮷(U+20BB7、下の線の方が長い吉)とか、𠮟(U+ 20B9F、口偏に七の「しかる」)とか、𩸽(U+29E3D、魚の「ほっけ」)とか。いわゆる、サロゲートペア文字。char型が16ビットのC#とかJavaでは結構はまるやつ。
現状のC#は、サロゲートペアの識別子を一切認めていません。Roslynの実装でもそう。
ということで、こいつを見てくれ。
using System;
class
Program
{
static
void Main()
{
var
ᛊ = 2; // ルーン文字識別子。Other Letter。
var ɲ = 3; // 国際音声記号。Lower-case Letter。
var ˠ = 5; // 国際音声補助記号。Modifier Letter。
var
ℏ = 7; // プランク定数。Lower-case Letter。
//var ꬁ = 11; // エチオピア文字拡張A ってところに入ってる文字。Other Letter。Unicode 7.0 で入った。Roslyn なら識別子に使える。
//var 𩸽 = 13; // サロゲートペアで有名な「ほっけ」。現行の C# はサロゲートペア識別子を認めてない…
Console.WriteLine(ᛊ * ɲ * ˠ * ℏ);// * ꬁ * 𩸽);
}
}
ルーン文字(ᛊ)とか音声記号(ɲ)はおろか、音声補助記号(ˠ)まで識別子として使えるというのに。𩸽は無理。
UnicodeCategory GetUnicodeCategory(string s, int index)
Roslynは、C#で実装したC#コンパイラーです。ということで、.NETの標準ライブラリでのUnicodeサロゲートペアの扱いがどうかという辺りをまず紹介。
char型がGetUnicodeCategoryという静的メソッドを持っていて、これでUnicodeカテゴリーを判定できます。が、16ビットなchar型のメソッドで、どうやってサロゲートペアのカテゴリー判定をするのかというと、そのための別オーバーロードもちゃんとあります。string型を引数にとるもの。
using System;
class
Program
{
static
void Main()
{
Console.WriteLine(char.GetUnicodeCategory(‘ᛊ‘)); // OtherLetter
Console.WriteLine(char.GetUnicodeCategory(‘ɲ’)); // LowercaseLetter
// C# の char は16ビット。サロゲートペアは2文字になる。
Console.WriteLine(char.GetUnicodeCategory(“𩸽“[0])); // Surrogate
// ちゃんと、サロゲートペア用のカテゴリー判定メソッドもあって、こっちなら OtherLetter。
Console.WriteLine(char.GetUnicodeCategory(“𩸽“, 0)); // OtherLetter
}
}
これで判定してもらえれば、サロゲートペア識別子使えるのに…
使い道は
とはいえ、ここで問題。使えてうれしい?
ちょっと前に、Swiftが識別子に絵文字を使えるってので少しだけ話題になりましたが。その後、実用した人いるんですかね?【緩募】絵文字識別子の有意義な使い方(割と真剣に)。
Swiftの絵文字識別子って実際のところどうなんでしょう?探してみたら、Swiftの識別子の仕様説明してくれてるページは見つけたものの(Swift言語の識別子についての覚え書き)、これだと絵文字使えないように見えるんですが…
で、http://www.swiftstub.com/ とかを使っていくつかコードを書いてみたら、むしろ、サロゲートペアは何でも素通しで識別子に使えてる節があったり。
そしてしばらく試してみて、「使えてうれしい例」は全然思いつかないけども、「できたらまずそう」な例はボロボロ思いついてしまうなど。
やばい例その1: http://www.swiftstub.com/381749597/
let 💙 = 1
let 💚 = 2
let 💛 = 4
let 💜 = 8
println(💙 + 💚 + 💛 + 💜)
上から順に、青ハート、緑ハート、黄ハート、紫ハート。カラーフォント対応したもので見れば色で区別がつくはず。が、白黒だとやばい。この文章を書いているWordでの表示は全部黒い。一応、横線、↘向きの斜線、↗向きの斜線、縦線で区別はされているものの、結構近づかないとわからない。
やばい例その2: http://www.swiftstub.com/647829248/
let 𝟢 = 1
let 𝟣 = 2
let 𝟤 = 4
let 𝟥 = 8
let 𝟦 = 16
let 𝟧 = 32
let 𝟨 = 64
let 𝟩 = 128
let 𝟪 = 256
let 𝟫 = 512
var x = 0
x += 𝟢
x += 𝟣
x += 𝟤
x += 𝟥
x += 𝟦
x += 𝟧
x += 𝟨
x += 𝟩
x += 𝟪
x += 𝟫
println(x)
「Mathematical Alphanumeric Symbols」って言って、U+1D400-1D7FF辺りに、数式で使う用の、フォント指定付きのアルファベットや数字があります。上記コードの0は、リテラルの方が普通の数字、変数に使ってる方が「MATHEMATICAL SANS-SERIF DIGIT」(サンセリフ フォント指定の数字)の𝟢(U+1D7E2)。コンパイル通った… 𝟢って書いたら0じゃなかった。何を言っているかわからねぇと思うが、書いた本人も後で見てわかる気がしねぇ。
カテゴリー判定は必須として
やばい例を避けるには、カテゴリー判定は必須だと思うんですが。
using System;
using System.Text;
class
Program
{
static
void Main()
{
ShowCategory(“𩸽“); // 𩸽 (U+29E3D): OtherLetter
ShowCategory(“𝟢“); // 𝟢 (U+1D7E2): DecimalDigitNumber
}
static
void ShowCategory(string s)
{
var bytes = Encoding.UTF32.GetBytes(s);
var x = (bytes[0] << 0) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
Console.WriteLine(“{0} (U+{1:X}): {2}”, s, x, char.GetUnicodeCategory(s, 0));
}
}
それはそれで。MATHEMATICAL SANS-SERIF DIGIT ZERO (U+1D7E2、𝟢)が普通に10進数扱い。ASCII文字の方の0と一緒。つまり、下手なことをすると、識別子2文字目以降に使える。
var x0 = 10; // ASCII の 0
var x𝟢 = 20; // U+1D7E2
これがコンパイル通るのはまずそう。なのでさらに、制限が必要そう。
ありえそうなのは、Unicode互換文字自体をはじくとか。でもそれをやると、最初の方で例に挙げた ℏ (U+ 210F、プランク定数)とかも使えなくなって、既存コンパイラーの挙動壊す。
もう1つできそうなのは、識別子の一意性判定にUnicode正規化を掛けるとか。U+1D7E2の方の𝟢は、Compatibility Decompositionで正規化するとASCIIの方の0になるので、「ぱっと見同じナノに別変数」みたいなのはできなくなるはず。
コスト/メリット合う?
何でこんなことを考えてるかというと、そもそもは、Roslynに対して絵文字対応とか提案できないかなぁとか思ったのがきっかけです。で、提案するからにはメリットがないと行けない。が、考えてみても特にメリット思い浮かばず、やばい例は思いつき、やばいものを避けるには結構コストがかかりそう(サロゲートペアのカテゴリー判定とか、識別子の一意性判定前に正規化とか)。そして何よりも、絵文字のカテゴリーはOther Symbolなので、カテゴリー判定すると識別子に使えないカテゴリー。絵文字は無理というかヤバいとしても、せめて𩸽だけでも…と思ったものの、それでもこのコストには見合うのか。特に、RoslynはIDE上でリアルタイムに動かす都合上、コンパイル速度に対して結構シビアなので。
うん、無理(いまここ)。
ということで、割かし真剣に
【緩募】絵文字識別子の有意義な使い方
【緩募】𠮷、𠮟、𩸽を識別子に使いたい実例
コメントを残す