これは_大きな_テーマです! これだけで長い本を1冊書くこともでき、実際に何人もの人が書いています(Amazonで「unicode book」を検索すると、いくつか例が見つかります)。
数十年前、コンピューターで文字を扱うのはもっとずっと簡単でした。当時のプログラマーは、英語だけが重要な言語だと考えていたからです。 大文字と小文字を合わせた26文字、10個の数字、いくつかの句読点、それにベルを鳴らすためのコード(0x07)まで、すべてが7ビットに収まりました。それがASCII文字セットです。
当然のことながら、àやä、Łはどうするのかという声が上がり、次にはऄやஹ、ญはどうかという声が上がり、そして若い人たちは絵文字😱を求めるようになりました。 どうすればいいのでしょうか?
手短に言えば、多くの賢く辛抱強い人たちが何年も委員会に参加して、Unicode文字セットやUTF-8のようなエンコーディングの細部を詰めていきました。そして、たくさんのソフトウェアに_とてつもなく_複雑な書き直しが必要になりました。 さらに、たくさんの新しいバグも生まれました。
_すべて_が壊れるのを防ぐため、Unicode/UTF-8の設計では、最初の127個のコードがASCIIとまったく同じになるようにしています(ベルのコードまで)。
2005年以降に設計された言語には、それなりに安定したUnicode標準がすでに存在していたという大きな利点があります。
Julia(2012年に最初のリリース)は、文字も文字列も変数名や関数名も、数学演算子まで、すべてがUnicodeであることを前提にできました。
マニュアルを引用すると、「Juliaは、通常のASCIIテキストの扱いを簡単かつ効率的にし、Unicodeの扱いも可能な限り簡単かつ効率的にします」となります。この「可能な限り」という部分が重要です。
文字リテラルはシングルクォートで書きます。ダブルクォートで書く文字列とは別のものです。
これはC/C++の世界の人には当たり前に思えますが、PythonやJavaScriptのプログラマーには戸惑うところかもしれません。
julia> a = 'a' # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> typeof(a)
Char
julia> jha = 'झ' # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)
julia> typeof(jha)
Char
julia> '❤' # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)
例から、型がCharであることがわかります。また、Juliaは文字のカテゴリについてもっと詳しい情報を持っています。
よく見ると、これらの文字は4桁の16進数で表せるようです。 文字セット全体では、最大6桁の16進数が必要になります。
このような数を「コードポイント」と呼び、現在はU+0000からU+10FFFFまでの範囲にあります。
REPLではそのまま表示されますが、コードの中ではcodepoints()を使うと取得できます。
CharとIntの間の変換は簡単です。
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
コンパイラーは、Charに対する整数演算の_一部_の形を認めています。
julia> 'b' - 'a' # interval, in alphabetic order
1
julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)
julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)
julia> 'f' + ('A' - 'a') # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)
文字列を扱う関数の一部は、Charの入力に対しても使えます。
uppercase()とlowercase()で大文字・小文字を変換できます。isuppercase()とislowercase()で調べられます。isletter()、多くのアルファベットに対応isdigit()、厳密に0〜9かどうかを調べますisnumeric()、isdigitより広く、¾やヨーロッパ以外のさまざまな文字体系でもtrueを返しますisxdigit()、16進数の数字isascii()、Unicode以前の文字ispunct()、句読点isspace()、任意の空白文字isprint()、表示可能な文字(逆はiscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
ある文字が文字列の中に含まれているかどうかを調べるには、inがあります。
これは部分文字列の場合とは違うことに注意してください。
# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true
# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment
また、正規表現(別の概念で扱うテーマです)を使えば、強力な検索や操作ができます。
CharのベクターとStringの相互変換StringからCharのベクターへは、collect()を使います。
CharのベクターからStringへは、String()コンストラクターがあります。
julia> s = "abcde"
"abcde"
julia> cv = collect(s)
5-element Vector{Char}:
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
julia> String(cv)
"abcde"
これはASCIIだけでなく、どんな文字でも使えます。
julia> collect("❤,😱")
3-element Vector{Char}:
'❤': Unicode U+2764 (category So: Symbol, other)
',': ASCII/Unicode U+002C (category Po: Punctuation, other)
'😱': Unicode U+1F631 (category So: Symbol, other)
String()コンストラクターはベクターに対して働くことに注意してください。
1つのCharを1文字の文字列に_キャスト_するには、小文字のsを使ったstring()という関数を使います。
julia> string('a')
"a"
ここまでに説明したことはどれも比較的シンプルに見えるので、本当に心配することはほとんどないのでしょうか?
残念ながら、それは楽観的すぎます!
複雑な点の1つは、コードポイントごとに「最大」6桁の16進数が必要になることから来ています。 つまり、UTF-8でエンコードすると、文字によってメモリー上で必要な領域の大きさが変わるということです。
1バイトで格納できるのは(符号なしで)255まで、つまり16進数2桁分だけなので、UTF-8では1つのCharを格納するのに可変のバイト数(1〜4)を使います。
これを「コード単位」と呼び、ncodeunits()関数を使うと、ある文字に必要な数が返ります。
julia> codepoint(jha) # jha 'झ' is defined in an earlier example
0x0000091d
julia> ncodeunits(jha)
3
julia> ncodeunits('a') # ASCII character
1
julia> ncodeunits('😱') # emoji
4
また、画面に表示できるものすべてが、それぞれ固有のコードポイントを持っているわけではありません。 見た目が異なる文字の中には、別の文字から派生したものとみなされるものがあり、Unicodeでは親の文字と修飾子の組み合わせとして扱われます。