هذا موضوع ضخم! يمكن كتابة كتاب طويل عنه، وقد فعل ذلك عدة أشخاص (ابحث في Amazon عن "unicode book" لترى بعض الأمثلة).
كان التعامل مع المحارف في الحاسوب أبسط بكثير في العقود الماضية، حين كان المبرمجون يفترضون أن الإنجليزية هي اللغة المهمة الوحيدة. إذن: 26 حرفًا، بحالتيه العلوية والسفلية، و10 أرقام، وعدة علامات ترقيم، إضافة إلى رمز (0x07) لرنين الجرس، وقد اتّسع ذلك كله في 7 بتات: مجموعة محارف ASCII.
وبطبيعة الحال، بدأ الناس يسألون عن à و ä و Ł، ثم بدأ آخرون يسألون عن ऄ و ஹ و ญ، وأراد الشباب إيموجي 😱. فماذا نفعل؟
ولاختصار القصة الطويلة، اضطر كثير من الأشخاص الأذكياء والصابرين إلى العمل في لجان لسنوات، يضعون تفاصيل مجموعة محارف Unicode، والترميزات مثل UTF-8، واحتاج كثير من البرمجيات إلى إعادة كتابة بالغة التعقيد. كما ظهرت Bug جديدة كثيرة.
ولمنع انهيار كل شيء، يضمن تصميم 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 معلومات إضافية عن فئة المحرف.
وبنظرة أدق، يبدو أن هذه المحارف يمكن تمثيلها بأربعة أرقام ست عشرية. أما مجموعة المحارف الكاملة فتحتاج إلى ما يصل إلى 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)
يسمح المترجم بـ بعض أشكال الحساب الصحيح على قيم Chars:
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()، أرقام ست عشريةisascii()، محرف يعود لما قبل Unicodeispunct()، علامات الترقيمisspace()، أي محرف مسافة بيضاءisprint()، المحارف القابلة للطباعة (وعكسها iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
Char والسلسلة النصيةللتحويل من السلسلة النصية إلى متجه المحارف، يمكننا استخدام collect().
وللتحويل من متجه المحارف إلى السلسلة النصية، يوجد المُنشئ 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"
كل ما ورد في هذا المستند حتى الآن يبدو بسيطًا نسبيًا، فهل لا يوجد حقًا ما يدعو للقلق؟
للأسف، هذا تفاؤل مفرط!
تنشأ إحدى التعقيدات من الحاجة إلى «ما يصل إلى» 6 أرقام ست عشرية لكل نقطة ترميز. وهذا يعني أن المحارف المختلفة تحتاج إلى مساحات مختلفة في الذاكرة عند ترميزها بـ UTF-8.
لا يمكن للبايت أن يخزّن إلا الأعداد (غير مُوقّعة) حتى 255، أي رقمين ست عشريين، لذا يستخدم UTF-8 عددًا متغيرًا من البايتات (من 1 إلى 4) لتخزين 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 كمحرف أساسي مضافًا إليه مُعدِّل.
تؤثر هذه المسألة في السلاسل النصية، حيث تطرح تحديات في الفهرسة.
في هذا التمرين ستُنفّذ مجموعة جزئية من الدوال المساعدة لمساعدة المطوّر على تنظيف أسماء المعرّفات.
في المهام الست ستبني تدريجيًا دالتَي transform لتحويل الأحرف المفردة وclean لتحويل السلاسل النصية.
يتكوّن المعرّف الصالح من صفر أو أكثر من الأحرف والشرطات السفلية والشرطات وعلامات الاستفهام والرموز التعبيرية.
إذا مُرّرت سلسلة نصية فارغة إلى دالة 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_?😀"
سجّل في Exercism لتتعلّم وتتقن Julia عبر 35 مفهومًا128 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.