این موضوع بزرگی است! میتوان دربارهی آن کتابی طولانی نوشت و چندین نفر هم این کار را کردهاند (در آمازون عبارت «unicode book» را جستوجو کنید تا چند نمونه ببینید).
پردازش نویسهها در رایانه در دهههای پیشین بسیار سادهتر بود، زمانی که برنامهنویسان فرض میکردند انگلیسی تنها زبان مهم است. پس: ۲۶ حرف، بزرگ و کوچک، ۱۰ رقم، چند علامت نگارشی، بهعلاوه یک کد (0x07) برای به صدا درآوردن زنگ، و همهی اینها در ۷ بیت جا میشد: مجموعهنویسهی ASCII.
طبیعی بود که مردم بپرسند پس تکلیف à، ä و Ł چیست، بعد افراد دیگری بپرسند دربارهی ऄ، ஹ و ญ چه کنیم و جوانترها هم ایموجی 😱 بخواهند. چه باید کرد؟
خلاصهاش این که بسیاری از افراد باهوش و صبور سالها در کمیتهها عضویت داشتند و جزئیات مجموعهنویسهی Unicode و کدگذاریهایی مثل UTF-8 را مشخص کردند و نرمافزارهای زیادی نیاز به بازنویسی بسیار پیچیدهای پیدا کردند. ضمناً باگهای جدید زیادی هم به وجود آمد.
برای این که همهچیز از کار نیفتد، طراحی Unicode/UTF-8 تضمین میکند که ۱۲۷ کد اول با ASCII یکسان باشند (حتی همان زنگ).
زبانهایی که بعد از حدود ۲۰۰۵ طراحی شدهاند این مزیت بزرگ را دارند که استاندارد Unicode نسبتاً پایداری از قبل وجود داشت.
Julia (که اولین بار در ۲۰۱۲ منتشر شد) میتوانست فرض کند که همهچیز 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 اطلاعات بیشتری دربارهی دستهی نویسه دارد.
اگر دقیقتر نگاه کنیم، میبینیم این نویسهها را میتوان با ۴ رقم هگزادسیمال نمایش داد. کل مجموعهنویسه به ۶ رقم هگز نیاز دارد.
به این اعداد «کد پوینت» میگویند و در حال حاضر از 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()، فقط ارقام ۰ تا ۹ را بررسی میکندisnumeric()، گستردهتر از isdigit، پس برای ¾ و خطوط غیراروپایی مختلف true برمیگرداندisxdigit()، ارقام هگزادسیمالisascii()، نویسهی پیش از Unicodeispunct()، علائم نگارشی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 میتوانیم از collect() استفاده کنیم.
برای تبدیل بردار Char به رشته، سازندهی 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() روی یک بردار کار میکند.
برای تبدیل یک Char تکی به رشتهای یکنویسهای، تابع string() است، با حرف کوچک s.
julia> string('a')
"a"
هر چه تا اینجا در این سند گفته شد نسبتاً ساده به نظر میرسد، پس واقعاً چیز زیادی برای نگرانی وجود ندارد؟
متأسفانه، این بیش از حد خوشبینانه است!
یک پیچیدگی از اینجا میآید که هر کد پوینت به «حداکثر» ۶ رقم هگز نیاز دارد. یعنی نویسههای مختلف وقتی با UTF-8 کدگذاری میشوند، مقدار متفاوتی از فضای حافظه را نیاز دارند.
یک بایت فقط میتواند اعداد (بدون علامت) تا ۲۵۵ را ذخیره کند، یعنی دو رقم هگز، پس UTF-8 برای ذخیرهی یک Char از تعداد متغیری بایت (۱ تا ۴) استفاده میکند.
به اینها «کد یونیت» میگویند و تابع 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 با آنها مثل یک نویسهی والد بههمراه یک تغییردهنده برخورد میکند.