पिछले छह महीनों से मैं रस्ट में NES एमुलेटर पर काम कर रहा हूँ। जैसा कि आप अनुमान लगा सकते हैं, मैंने रस्ट के बारे में बहुत कुछ सीखा है, और NES के आंतरिक कामकाज के बारे में तो और भी ज्यादा। लेकिन इस अनुभव ने रूबी को देखने का मेरा नजरिया भी बदल दिया है।
खासकर, इसने मुझे उन मेथड्स के बारे में बेहद सतर्क कर दिया है जो nil लौटाते हैं।
यदि आप किसी चीज़ के लिए खड़े नहीं होते, तो आप किसी भी चीज़ के लिए गिर जाएंगे
रूबी में nil का क्या अर्थ है? लगभग कुछ भी। जब कोई मेथड nil लौटाता है, तो इसका मतलब हो सकता है:
- मेथड का कोई रिटर्न वैल्यू नहीं है
- आमतौर पर एक रिटर्न वैल्यू होता है लेकिन इस बार नहीं
- यह डेटाबेस से एक वैल्यू लौटाता है, जो NULL है
- कुछ अप्रत्याशित हुआ है
इससे कोड पढ़ना मुश्किल हो जाता है और यह रूबी में सबसे आम अपवाद NoMethodError का मुख्य कारण है। एक एक्सेप्शन मॉनिटरिंग सर्विस के पार्ट-ओनर के रूप में, NoMethodError मेरे बच्चे की स्कूल फीस भर रहा है।
निम्नलिखित कोड देखें। यह ज्यादातर समय nil लौटाता है क्योंकि if स्टेटमेंट्स nil का मूल्यांकन करते हैं जब कंडीशनल मेल नहीं खाता और कोई else नहीं होता।
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 का उपयोग करते हैं।
Options एक ऐसा टाइप है जिसमें या तो कोई विशिष्ट मान होता है, या कोई मान नहीं होता। कोड में ये इस तरह दिखते हैं:
Option::Some(42); // संख्या 42 को एक ऑप्शन में लपेटता है
Option::None; // "कोई परिणाम नहीं" दर्शाता है
यह पहले से ही हमारे तदर्थ nil उपयोग से बेहतर दिख रहा है, लेकिन यह और भी बेहतर हो जाता है। रस्ट कंपाइलर आपको None केस पर विचार करने के लिए मजबूर करता है। आप इसे गलती से नज़रअंदाज़ नहीं कर सकते।
match my_option {
Some(x) => do_something_with_x(x),
// यदि आप नीचे `None` मैच को हटा देते हैं, तो यह कोड
// कंपाइल नहीं होगा।
None => do_the_default_thing()
}
तो हम अपने कलर नेमिंग उदाहरण को रस्ट में इस तरह लिख सकते हैं:
fn color_name(rgb: u32) -> Option<String> {
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(),
}
निश्चित रूप से यह थोड़ा वर्बोज़ और अजीब दिखता है, लेकिन यह उस केस को नज़रअंदाज़ करना असंभव बना देता है जब कोई फंक्शन उपयोगी मान नहीं लौटाता। इसका मतलब है ऐसा कोड जिसे समझना और बनाए रखना आसान हो।
रूबी में 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" प्रिंट करता है
निष्कर्ष
मैंने अभी तक इस तकनीक को किसी वास्तविक प्रोजेक्ट में आज़माया नहीं है। मुझे लगता है कि यह निश्चित रूप से उन कई NoMethodErrors को रोक सकता है जो हमेशा प्रोडक्शन में घुस जाते हैं। यह देखने में थोड़ा बोझिल है, और बहुत रूबी-स्टाइल नहीं है, लेकिन मुझे लगता है कि कुछ सुधार के साथ एक अधिक सुखद सिंटैक्स उभरेगा।