これは_大きな_テーマです! それについて長い本を一冊書くこともできますし、実際に何人もの人が書いています(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は文字のカテゴリーについての詳しい情報も持っています。
よく見てみると、これらの文字は16進数4桁で表せるようです。 文字セット全体となると、最大で16進数6桁が必要です。
これらの数値は「コードポイント」と呼ばれ、現在は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
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つの問題は、コードポイントごとに「最大」16進数6桁が必要だという点から来ています。 つまり、UTF-8でエンコードすると、文字によってメモリ上で必要な容量が異なります。
1バイトは(符号なしの)数値を255まで、つまり16進数2桁までしか格納できないので、UTF-8は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では親となる文字と修飾子の組み合わせとして扱われます。
この問題は文字列にも影響し、インデックス付けに難しさをもたらします。
この演習では、エンジニアが識別子の名前を整えるのを助けるための、ユーティリティルーチンの一部を実装します。
6つのタスクを通して、1文字を変換するtransformと、文字列を変換するcleanという関数を少しずつ作り上げていきます。
有効な識別子は、0個以上の文字、アンダースコア、ハイフン、疑問符、絵文字で構成されます。
clean関数に空文字列を渡した場合は、空文字列が返されるようにします。
transform関数を実装して、ハイフンをアンダースコアに置き換えます。
julia> transform('-')
"_"
空白文字をすべて削除します。 先頭と末尾の空白も含みます。
julia> transform(' ')
""
transform関数を変更して、camelCaseをkebab-caseに変換します。
julia> transform('D')
"-d"
transform関数を変更して、数値である文字を除外します。
julia> transform('7')
""
transform関数を変更して、'α'から'ω'の範囲のギリシャ文字を置き換えます。
julia> transform('β')
"?"
clean関数を実装して、これらの操作を文字列全体に適用します。
ルールに当てはまらない文字は、そのまま変更せずに通します。
julia> clean(" a2b Cd-ω😀 ")
"ab-cd_?😀"