Crystal-এ একটি ভ্যারিয়েবল একাধিক টাইপের সমন্বয়ে গঠিত হতে পারে। এটাকে ইউনিয়ন টাইপ বলা হয়। Crystal-এ কম্পাইলার সাধারণত ইউনিয়ন টাইপ নিজে থেকেই অনুমান করে নেয়।
রানটাইমে একটি ইউনিয়ন টাইপ আসলে একটি একক টাইপই, যদিও এটি একাধিক টাইপ দিয়ে তৈরি হতে পারে। এর মানে হলো, যদি একটি ইউনিয়ন টাইপ String ও Int32 দিয়ে গঠিত হয়, তাহলে এটি একই সময়ে দুটোই থাকবে না। বরং এটি একটি String অথবা একটি Int32 হবে।
একটি ইউনিয়ন টাইপ ডিক্লেয়ার করা হয় টাইপগুলোর মাঝে পাইপ (|) বসিয়ে। এগুলোকে অনেক সময় বন্ধনীর ভেতরে রাখা হয়, তবে এটা বাধ্যতামূলক নয়। সবচেয়ে কমন ইউনিয়ন টাইপ হলো (T | Nil), যেখানে T একটি টাইপ। এমন মেথডে এটি দেখা যায় যেগুলো Nil রিটার্ন করতে পারে। এটাকে ভ্যারিয়েবলের নিলেবল হওয়াও বলা হয়।
foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)
foo : (String | Nil) = "Hello"
foo = nil
এটি শুধু দুইটি টাইপেই সীমাবদ্ধ নয়, আপনি যত চান তত টাইপ দিতে পারেন।
foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0
typeof বনাম Object#class
একটি ভ্যারিয়েবলের টাইপ পাওয়ার দুটি উপায় আছে। হয় typeof ব্যবহার করে, নয়তো Object#class ব্যবহার করে। পার্থক্য হলো, typeof কম্পাইল টাইমে ভ্যারিয়েবলের টাইপ রিটার্ন করে, আর Object#class রানটাইমে টাইপ রিটার্ন করে। এর মানে হলো, আপনি যদি দেখতে চান একটি ভ্যারিয়েবল ইউনিয়ন টাইপ কি না, উদাহরণস্বরূপ, Object#class সেটা বলতে পারবে না, কারণ এটি শুধু রানটাইমে টাইপ রিটার্ন করে, যেটি একটি একক টাইপ।
foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String
যেহেতু ইউনিয়ন টাইপ রানটাইমে একটি একক টাইপ, তাই এর উপর সব স্ট্যান্ডার্ড অপারেশন কাজ করে। কিন্তু কোড কম্পাইল করার সময় কম্পাইলারকে জানতে হয় এটি কোন টাইপ। তাই টাইপ-নির্দিষ্ট অপারেশন ব্যবহার করতে চাইলে কোড এমনভাবে সাজাতে হয় যাতে এটি শুধু একটি টাইপই হতে পারে।
foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)
Crystal-এ ইউনিয়ন টাইপের জন্য একটি বিশেষ মেথড আছে: is_a? মেথড, যেটি আর্গুমেন্ট হিসেবে একটি টাইপ নেয় এবং একটি বুলিয়ান রিটার্ন করে। nil? মেথডটি is_a?(Nil)-এর একটি শর্টকাট। is_a? মেথডটি একটি কন্ট্রোল এক্সপ্রেশনে বসালে কম্পাইলারকে জানিয়ে দেবে এটি কোন টাইপ, আর তার ফলে নিশ্চিত হবে যে এটি ওই টাইপই। আর else শাখার ক্ষেত্রে নিশ্চিত হবে যে এটি ওই টাইপ নয়।
foo : (String | Int32) = "Hello"
if foo.is_a?(String)
typeof(foo) # => String
foo.downcase # => "hello"
end
এই is_a? শুধু একটি টাইপ আর্গুমেন্ট নিয়েই সীমাবদ্ধ নয়, এটি ইউনিয়ন টাইপও নিতে পারে। একাধিক টাইপের জন্য এটি &&-এর সাথে একসাথে ব্যবহার করা যায়।
কন্ট্রোল এক্সপ্রেশনের সাথে ব্যবহার করার সময় is_a? মেথডটি ইনস্ট্যান্স ভ্যারিয়েবল বা ক্লাস ভ্যারিয়েবল হতে পারে না। বরং এগুলোকে প্রথমে একটি লোকাল ভ্যারিয়েবলে অ্যাসাইন করতে হবে।
ইউনিয়ন টাইপকে একক টাইপে পরিণত করার একটি উপায় হলো এমনভাবে সাজানো যাতে কোনো শাখায় প্রবেশ করা যায় কেবল তখনই, যখন টাইপটি একটি নির্দিষ্ট টাইপ হয়। আরেকটি উপায় হলো as মেথড ব্যবহার করা। এটি রানটাইম চেক করে ইউনিয়ন টাইপকে একক টাইপে পরিণত করে। প্রত্যাশিত টাইপ না হলে একটি এক্সেপশন রেইজ করা হয়।
foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"
foo.as(Int32) # Error: can't cast String to Int32
এই পদ্ধতিটি কেবল তখনই ব্যবহার করার কথা, যখন আপনি নিশ্চিত যে টাইপটি প্রত্যাশিত টাইপ, অথবা যখন টাইপটি প্রত্যাশিত না হলে আপনি একটি এক্সেপশন রেইজ করতে চান।
অনুপযুক্ত সেটআপে এই পদ্ধতি ব্যবহার করলে অপ্রত্যাশিত আচরণ হতে পারে।
as? প্রায় as-এর মতোই কাজ করে, তবে প্রত্যাশিত টাইপ না হলে এক্সেপশন রেইজ করার বদলে এটি nil রিটার্ন করে। এর মানে হলো, এটি প্রত্যাশিত টাইপ ও Nil-এর একটি ইউনিয়ন টাইপ রিটার্ন করে।
foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"
foo.as?(Int32) # => nil
নিলেবল মানে হলো একটি ভ্যারিয়েবল হয় কোনো একটি টাইপ হতে পারে, নয়তো Nil। এটা (T | Nil) হিসেবে লেখা যায়। কিন্তু যেহেতু নিলেবল টাইপ তুলনামূলকভাবে কমন, তাই এর একটি শর্টকাট আছে: T?।
# This:
foo : (String | Nil) = "Hello"
foo = nil
# Is the same as:
foo : String? = "Hello"
foo = nil
আপনি যে কোম্পানিতে কাজ করেন, তারা সদ্য তাদের একেবারে নতুন স্মার্টফোনটি বাজারে আনতে যাচ্ছে, যার নাম Smarty 5। এতে আছে সম্পূর্ণ নতুন একটি ক্যামেরা সিস্টেম, নতুন একটি ডিজাইন, আর smartyOS নামের নতুন একটি অপারেটিং সিস্টেমে চলা এক বিপ্লবী নতুন প্রসেসর।
নতুন প্রসেসরটির ক্ষমতা এতটাই যে এটি আগের যেকোনো সময়ের চেয়ে বেশি নিরাপদ পাসওয়ার্ড সামলাতে পারে, আর কোম্পানি সিদ্ধান্ত নিয়েছে যে তারা এই সুবিধাটি কাজে লাগাবে। তারা আপনাকে ফোনের জন্য একটি পাসওয়ার্ড লক সিস্টেম বানাতে বলেছে, যেখানে ব্যবহারকারী একটি পাসওয়ার্ড সেট করতে পারবেন এবং পরে যাচাই করা যাবে যে কোনো একটি পাসওয়ার্ড সঠিক কি না।
এই অনুশীলনীতে যে পাসওয়ার্ড সিস্টেমটি অনুশীলন করা হয়েছে তা নিরাপদ নয় এবং শুধু শেখানোর উদ্দেশ্যেই ব্যবহার করা হয়েছে। এটি কোনো বাস্তব অ্যাপ্লিকেশনে ব্যবহার করা উচিত নয়।
ফোনটি ব্যবহারকারীদের বিভিন্ন ধরনের পাসওয়ার্ড ব্যবহারের সুযোগ দেয়, সাধারণ ডিজিট পাসওয়ার্ড থেকে শুরু করে আরও নিরাপদ আলফানিউমেরিক পাসওয়ার্ড, এমনকি ব্যবহারকারীর ফিঙ্গারপ্রিন্ট সংরক্ষণ করা পাসওয়ার্ডও।
এই বিভিন্ন ধরনের পাসওয়ার্ডে ব্যবহার হয় ভিন্ন ভিন্ন টাইপ: ডিজিট পাসওয়ার্ড ব্যবহার করে Int32, আলফানিউমেরিক পাসওয়ার্ড ব্যবহার করে String, আর ফিঙ্গারপ্রিন্ট পাসওয়ার্ড ব্যবহার করে Float64।
তিন ধরনের পাসওয়ার্ডের যেকোনোটি আর্গুমেন্ট হিসেবে নিয়ে সেটিকে @password নামের একটি ইনস্ট্যান্স ভ্যারিয়েবলে সংরক্ষণ করে, এমন একটি ইনিশিয়ালাইজারসহ PasswordLock ক্লাসটি ইমপ্লিমেন্ট করুন।
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
কোম্পানি সিদ্ধান্ত নিয়েছে যে পাসওয়ার্ডটি যেন প্লেইন টেক্সটে সংরক্ষিত না থাকে, তাই সেটি এনক্রিপ্ট করা হবে, আর তারা আপনাকে পাসওয়ার্ড এনক্রিপ্ট করার একটি মেথড ইমপ্লিমেন্ট করতে বলেছে।
প্রতিটি পাসওয়ার্ড টাইপ এনক্রিপ্ট হওয়ার নিজস্ব একটি পদ্ধতি রয়েছে:
Int32 পাসওয়ার্ড: পাসওয়ার্ডটিকে দুই দিয়ে ভাগ করা হয় এবং নিকটতম ইন্টিজারে রাউন্ড করা হয়।String পাসওয়ার্ড: পাসওয়ার্ডটি রিভার্স করা হয়।Float64 পাসওয়ার্ড: পাসওয়ার্ডটিকে চার দিয়ে গুণ করা হয়।encrypt নামের একটি ইনস্ট্যান্স মেথড ইমপ্লিমেন্ট করুন, যা কোনো আর্গুমেন্ট নেয় না এবং @password ইনস্ট্যান্স ভ্যারিয়েবলটি এনক্রিপ্ট হয়ে যাওয়ার মতো করে পরিবর্তন করে।
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>
কোম্পানি আপনাকে আরও একটি মেথড ইমপ্লিমেন্ট করতে বলেছে, যা দিয়ে যাচাই করা যাবে যে প্রদত্ত পাসওয়ার্ডটি সঠিক কি না।
তারা চান, পাসওয়ার্ড ভুল হলে মেথডটি nil রিটার্ন করবে আর পাসওয়ার্ড সঠিক হলে "Unlocked" রিটার্ন করবে।
যাচাই করার পাসওয়ার্ডটি আর্গুমেন্ট হিসেবে নেয়, এমন একটি ইনস্ট্যান্স মেথড unlock? ইমপ্লিমেন্ট করুন।
পাসওয়ার্ড ভুল হলে মেথডটি nil রিটার্ন করবে আর পাসওয়ার্ড সঠিক হলে "Unlocked" রিটার্ন করবে।
password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Exercism-এ সাইন আপ করুন, Crystal ট্র্যাকের 26টি কনসেপ্ট142টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।