গত ছয় মাস ধরে আমি রাস্ট (Rust) ভাষায় একটি NES এমুলেটর তৈরি করছি। আপনি অনুমান করতে পারেন, আমি রাস্ট সম্পর্কে অনেক কিছু শিখেছি, আর NES-এর অভ্যন্তরীণ কাঠামো সম্পর্কে তার চেয়ে বেশি। কিন্তু এই অভিজ্ঞতাটি রুবি (Ruby) দেখার আমার দৃষ্টিকোণও বদলিয়েছে।
বিশেষ করে, এটি আমাকে nil রিটার্ন করার পদ্ধতিগুলো নিয়ে একটু চিন্তিত করে দিয়েছে।
যদি আপনি কোনো কিছুর পক্ষে দাঁড়ান না, তবে আপনি যেকোনো কিছুর ফাঁদে পড়বেন
রুবিতে nil বলতে কী বোঝায়? প্রায় যেকোনো কিছু। যখন কোনো মেথড nil রিটার্ন করে, তখন সেটি বোঝাতে পারে:
- মেথডটির কোনো রিটার্ন মান নেই
- সাধারণত রিটার্ন মান থাকে, কিন্তু এইবার নেই
- ডাটাবেস থেকে একটি মান রিটার্ন করছে, যা
NULL - কোনো অপ্রত্যাশিত ঘটনা ঘটেছে
এটি কোড পড়াকে কঠিন করে তোলে এবং রুবিতে সবচেয়ে সাধারণ এক্সসেপশন NoMethodError-এর প্রধান কারণ। একটি এক্সসেপশন মনিটরিং সার্ভিসের অংশীদার হিসেবে, NoMethodError আমার ছেলের স্কুল ফিস দিচ্ছে।
নিচের কোডটি দেখুন। এটি অধিকাংশ সময় nil রিটার্ন করে, কারণ if স্টেটমেন্টে কন্ডিশনাল ম্যাচ না করলে এবং else না থাকলে সেটি nil এvaluered হয়।
def color_name(rgb)
if rgb == 0x000000
"black"
end
end
color_name("#FFFFFF").titleize
# => NoMethodError: undefined method `titleize' for nil:NilClass
আপনি যদি অভিজ্ঞ রুবি ডেভেলপার হন, তবে জানেন এই খরগোশের বিল আরও গভীর। কখনো কখনো nil-এর এই বিভিন্ন অর্থ একে অপরের সাথে অদ্ভুতভাবে ওভারল্যাপ করে, ফলে জানা যায় না—উদাহরণস্বরূপ—ডাটাবেসে কোনো মান NULL, নাকি ডাটাবেসে কোনো মানই নেই।
একটি উত্তম উপায়
রাস্টে nil জিনিসটা নেই। এর পরিবর্তে, যখন আমরা জানাতে চাই যে কোনো ফাংশন কখনো মান রিটার্ন করে আবার কখনো "কিছুই না" রিটার্ন করে, তখন আমরা Option ব্যবহার করি।
Option হলো এমন একটি টাইপ যা হয় কোনো নির্দিষ্ট মান ধারণ করে, অথবা কোনো মানই ধারণ করে না। কোডে এটি কেমন দেখায়:
Option::Some(42); // ৪২ নাম্বারটি একটি অপশনে র্যাপ করে
Option::None; // "কোনো ফলাফল নেই" ইঙ্গিত করে
এইটা ইতিমধ্যে আমাদের-hoc nil ব্যবহার চেয়ে ভালো দেখাচ্ছে, কিন্তু এখনো ভালো কিছু বাকি। রাস্ট কম্পাইলার আপকে বাধ্য করে None কেসটি বিবেচনা করতে। আপনি ভুলেও এটিকে উপেক্ষা করতে পারবেন না।
match my_option {
Some(x) => do_something_with_x(x),
// যদি নিচের `None` ম্যাচটি সরিয়ে দেন, তবে কোড
// কম্পাইলই হবে না।
None => do_the_default_thing()
}
সুতরাং আমরা আমাদের রঙের নামকরণ উদাহরণটিকে রাস্টে এভাবে লিখতে পারি:
fn color_name(rgb: u32) -> Option {
if rgb == 0x000000 {
Some("black".to_owned())
} else {
None
}
}
এখন আমরা Some এবং None উভয় কেস হ্যান্ডল করার জন্য বাধ্য:
let name = color_name(0xFFFFFF);
let name = match color_name(0xFFFFFF) {
Some(value) => value,
None => "unknown".to_owned(),
}
হ্যাঁ, এটা একটু verbose এবং অদ্ভুত লাগতে পারে, কিন্তু এটি অসম্ভব করে দেয় যে কোনো ফাংশন উপকারী মান রিটার্ন না করলে সেটি উপেক্ষা করা যায়। অর্থাৎ কোড বুঝা এবং মেইনটেইন করা সহজ হয়ে যায়।
রুবিতে Option ইমপ্লিমেন্ট করা
রুবি কঠোরতায় যেটা না পায়, সেটা লচিলাপনে পুরা করে। আমি ভাবলাম রুবিতে Option-এর মতো কিছু ইমপ্লিমেন্ট করার চেষ্টা করা দারুণ হবে।
রুবি ইন্টারপ্রেটেড ভাষা হলে আমরা কম্পাইল-টাইম এরর তৈরি করতে পারি না। কিন্তু ভুল কোডকে সবসময় এক্সসেপশন থ্রো করতে বাধ্য করতে পারি, শুধু এজ কেস হিট হলে নয়।
প্রথমে, চলুন দুটি ক্লাস তৈরি করি। Some একটি রিড-অনلی মান ধারণ করে। None খালি। তারা দেখতে যেন জটিল, আসলে সিম্পল।
class Some
attr_reader :value
def initialize(value)
@value = value
end
end
class None
end
এরপর, আমরা আমাদের Option ক্লাস তৈরি করব যেটি হয় Some হয় None ধারণ করবে এবং আমাদেরকে তাদের অ্যাক্সেস করতে দেবে তখনই যখন আমরা উভয় কেসের জন্য হ্যান্ডলার দেব।
class Option
def initialize(value)
@value = value
end
def self.some(value)
self.new(Some.new(value))
end
def self.none()
self.new(None.new)
end
def match(some_lambda, none_lambda)
if @value.is_a?(Some)
some_lambda.call(@value.value)
elsif @value.is_a?(None)
none_lambda.call()
else
raise "Option value must be either Some or None"
end
end
end
শেষে, আমরা আমাদের রঙের উদাহরণটিকে নতুন Option ক্লাস ব্যবহার করে পুনর্লিখন করতে পারি:
def color_name(rgb)
if rgb == 0x000000
Option.some("black")
else
Option.none()
end
end
puts color_name(0x000000).match(
-> value { value },
-> { "no match" }
) # প্রিন্ট করে "black"
উপসংহার
আমি এখনো এই টেকনিকটি কোনো রিয়েল প্রজেক্টে ট্রাই করিনি। আমি মনে করি এটি প্রোডাকশনে যেন যেন ঢুকে পড়া NoMethodError গুলো রোধ করতে পারে। এটা একটু clumsy দেখায় এবং খুব রুবি-ইশ (Rubyish) নয়, কিন্তু কিছু রিফাইনমেন্টের পর আরও সুন্দর সিনট্যাক্স আসার আশা করি।