এটি একটি বিশাল বিষয়! এর উপর একটি মোটা বই লেখা সম্ভব, আর কয়েকজন তা করেছেনও (কিছু নমুনা দেখতে Amazon-এ "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(), কঠোরভাবে 0:9 পরীক্ষা করেisnumeric(), isdigit-এর চেয়ে ব্যাপক, তাই ¾ আর নানা অ-ইউরোপীয় লিপির জন্য true
isxdigit(), হেক্সাডেসিমাল ডিজিট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 Vector-এ যেতে collect() ব্যবহার করা যায়।
Char Vector থেকে স্ট্রিং-এ যেতে আছে 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() কনস্ট্রাক্টর কাজ করে একটি Vector-এর উপর।
একটি Char-কে কাস্ট করে এক-ক্যারেক্টারের স্ট্রিং বানাতে হলে ফাংশনটি string(), ছোট হাতের s দিয়ে।
julia> string('a')
"a"
এতক্ষণের সবকিছু দেখে মনে হয় বেশ সহজ, তাহলে কি সত্যিই চিন্তার কিছু নেই?
দুঃখজনকভাবে, এটা খুব বেশি আশাবাদী হয়ে যাওয়া!
একটি জটিলতা আসে প্রতি কোড পয়েন্টে "সর্বোচ্চ" ৬টি হেক্স ডিজিট লাগার দরকার থেকে। এর মানে হলো, UTF-8 এনকোড করা হলে বিভিন্ন ক্যারেক্টারের জন্য মেমরিতে বিভিন্ন পরিমাণ জায়গা লাগে।
একটি বাইট (unsigned) সংখ্যা রাখতে পারে সর্বোচ্চ ২৫৫ পর্যন্ত, অর্থাৎ দুটি হেক্স ডিজিট, তাই 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 তাদের একটি মূল ক্যারেক্টার আর একটি মডিফায়ার হিসেবে দেখে।
এই সমস্যাটি স্ট্রিং-এর ক্ষেত্রে প্রভাব ফেলে, যেখানে এটি ইনডেক্সিংয়ের জন্য চ্যালেঞ্জ তৈরি করে।
এই অনুশীলনীতে আপনি কিছু ইউটিলিটি রুটিনের একটি অংশ বাস্তবায়ন করবেন, যা একজন ডেভেলপারকে আইডেন্টিফায়ার নাম পরিষ্কার করতে সাহায্য করবে।
৬টি টাস্কে আপনি ধীরে ধীরে ফাংশন দুটি তৈরি করবেন: transform, যা একক ক্যারেক্টার রূপান্তর করে, এবং clean, যা স্ট্রিং রূপান্তর করে।
একটি বৈধ আইডেন্টিফায়ারে শূন্য বা তার বেশি অক্ষর, আন্ডারস্কোর, হাইফেন, প্রশ্নচিহ্ন ও ইমোজি থাকতে পারে।
clean ফাংশনে যদি একটি খালি স্ট্রিং পাঠানো হয়, তাহলে একটি খালি স্ট্রিং রিটার্ন করা উচিত।
যেকোনো হাইফেনকে আন্ডারস্কোর দিয়ে প্রতিস্থাপন করতে transform ফাংশনটি বাস্তবায়ন করুন।
julia> transform('-')
"_"
সব হোয়াইটস্পেস ক্যারেক্টার মুছে ফেলুন। এর মধ্যে শুরু ও শেষের হোয়াইটস্পেসও থাকবে।
julia> transform(' ')
""
camelCase-কে kebab-case-এ রূপান্তর করতে transform ফাংশনটি পরিবর্তন করুন
julia> transform('D')
"-d"
যেকোনো সংখ্যাসূচক ক্যারেক্টার বাদ দিতে transform ফাংশনটি পরিবর্তন করুন।
julia> transform('7')
""
'α' থেকে 'ω' পর্যন্ত পরিসরের যেকোনো গ্রিক অক্ষর প্রতিস্থাপন করতে transform ফাংশনটি পরিবর্তন করুন।
julia> transform('β')
"?"
সম্পূর্ণ একটি স্ট্রিংয়ে এই অপারেশনগুলো প্রয়োগ করতে clean ফাংশনটি বাস্তবায়ন করুন।
যেসব ক্যারেক্টার নিয়মের বাইরে পড়ে, সেগুলো অপরিবর্তিত অবস্থায় পার হয়ে যাবে।
julia> clean(" a2b Cd-ω😀 ")
"ab-cd_?😀"
Exercism-এ সাইন আপ করুন, Julia ট্র্যাকের 35টি কনসেপ্ট128টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।