Magnolia Tech

いつもコードのことばかり考えている人のために。

Scalaの識別子(クラス名、メソッド名、変数名)に使える文字・使えない文字

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コンパイラは、識別子に使える文字を、JavaCharacter.isUnicodeIdentifierStartと、Character.isUnicodeIdentifierPartを使って判定しています。

文字通りStartが1文字目を判定し、Partが2文字目以降を判定するのに使われます。

ドキュメントから少し引用してみましょう。

Character.isUnicodeIdentifierStart

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html#isUnicodeIdentifierStart(char)

  • 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.

isLetterJavaの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を引数に取る方ではUnicodesupplementary charactersがサポートされていないのです。

Character.isUnicodeIdentifierPart

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html#isUnicodeIdentifierPart(char)

次に、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について知りたい方は、下記の本がおすすめです。全部書いてあります。