Scalaでは、クラス名、メソッド名、変数名などの「識別子」に漢字・ひらがな・カタカナが使えます。
scala> val 変数 = 42 val 変数: Int = 42 scala> def 引数を倍にするメソッド(引数: Int): Int = 引数 * 2 def 引数を倍にするメソッド(引数: Int): Int scala> val 答え = 引数を倍にするメソッド(変数) val 答え: Int = 84
でも意外と使えない文字が有ります。
scala> val 質問・回答 = "しつもん・かいとう" 1 |val 質問・回答 = "しつもん・かいとう" | ^ | illegal character '\u30fb' 1 |val 質問・回答 = "しつもん・かいとう" | ^ | expression expected but eof found scala> val 𠮷野家 = "よしのや" 1 |val 𠮷野家 = "よしのや" | ^ | illegal character '\ud842' 1 |val 𠮷野家 = "よしのや" | ^ | illegal character '\udfb7' 1 |val 𠮷野家 = "よしのや" | ^ | '=' expected, but eof found
1つ目は、"・(中黒)"が使われています。
2つ目は、”𠮷野家"の1文字目が"吉"ではなく"𠮷"になっています(看板の通りですね)。
識別子に使える文字は?
Scalaのコンパイラは、識別子に使える文字を、JavaのCharacter.isUnicodeIdentifierStart
と、Character.isUnicodeIdentifierPart
を使って判定しています。
文字通りStart
が1文字目を判定し、Part
が2文字目以降を判定するのに使われます。
ドキュメントから少し引用してみましょう。
Character.isUnicodeIdentifierStart
- isLetter(codePoint) returns true
- getType(codePoint) returns LETTER_NUMBER. Note: This method cannot handle supplementary characters. To support all Unicode characters, including supplementary characters, use the isUnicodeIdentifierStart(int) method.
isLetter
もJavaのCharacterクラスが提供するメソッドで、以下の文字が対象となります。
- UPPERCASE_LETTER
- LOWERCASE_LETTER
- TITLECASE_LETTER
- MODIFIER_LETTER
- OTHER_LETTER
つまり、UnicodeのCategoryにおける6種類の属性を持つものが利用できることが分かります。漢字やひらがな、カタカナ等はほとんど対応しますね。
また、Scalaでは、記号のみから成る識別子を特別扱いしていて、Character.isUnicodeIdentifierStart
の判定とは別に特定のASCII記号と、Unicodeの「Symbol, Other」「Symbol, Math」に該当する記号のみから成る識別子を許可しています。
例えば、以下の変数名は有効です(絵文字は「Symbol, Other」に該当する)。
scala> val ☃️ = "ゆきだるま" val ☃: String = ゆきだるま
そして、最後に大事なことが書かれていますね。そう、Character.isUnicodeIdentifierStart
にはchar
を引数に取るものと、int
を引数に取るものの2種類があり、char
を引数に取る方ではUnicodeのsupplementary characters
がサポートされていないのです。
Character.isUnicodeIdentifierPart
次に、Character.isUnicodeIdentifierPart
を見てみましょう。
- it is a letter
- it is a connecting punctuation character (such as '_')
- it is a digit
- it is a numeric letter (such as a Roman numeral character)
- it is a combining mark
- it is a non-spacing mark
- isIdentifierIgnorable returns true for this character. Note: This method cannot handle supplementary characters. To support all Unicode characters, including supplementary characters, use the isUnicodeIdentifierPart(int) method.
少し使える文字が増えました。数字やアンダースコアや、が増えているのがわかるかと思います。
ただ、表記方法が違うので分かりづらいですが、Character.isUnicodeIdentifierStart
で使える文字は全て使えます。
そして、やはり引数にchar
を取る方では、supplementary characters
がサポートされていません。
なぜ使えないか?
中点(なかぐろ)
https://www.compart.com/en/unicode/U+30FB
こちらを参照すると分かりますが、Unicodeのカテゴリが「Other Punctuation」になっているためです。
𠮷野家
𠮷野家の「𠮷」は「Supplementary Ideographic Plane」に属しています。
https://www.compart.com/en/unicode/U+20BB7
なので、現在の実装では正しく判定できません。
Scalaのコンパイラがサロゲートペア文字を正しく認識し、BMP(Basic Multilingual Plane)以外の文字も識別子として扱えるようになればいいのですが。
同様の理由で、Unicodeの絵文字群のうち、かなりの文字が「supplementary characters」なので、識別子として使えません。先程の☃️は使えましたが、顔文字はダメですね。
scala> val 😂 = "泣いて笑って" 1 |val 😂 = "泣いて笑って" | ^ | illegal character '\ud83d' 1 |val 😂 = "泣いて笑って" | ^ | illegal character '\ude02' 1 |val 😂 = "泣いて笑って" | ^ | '=' expected, but eof found
というタイミングで、Scala2に「supplementary characters」を使えるようにするPRが作られました。
Accept supplementary characters by som-snytt · Pull Request #9687 · scala/scala · GitHub
Scala3にもポートされることが期待されています。
早く使えるようになるといいですね!
文字コードへの理解
絵文字の普及でBMP以外の面のサポートがだいぶ進みましたが、まだまだ意外なところでサポートが進んでいないことがわかりますね。
BMPとか、supplementary charactersについて知りたい方は、下記の本がおすすめです。全部書いてあります。