रूबी मैजिक में हम उन चीजों के पीछे के जादू में गोता लगाना पसंद करते हैं जिनका हम हर दिन उपयोग करते हैं ताकि यह समझ सकें कि वे कैसे काम करती हैं। इस संस्करण में, हम ब्लॉक्स, प्रॉक्स और लैम्ब्डा के बीच अंतर का पता लगाएंगे।
फर्स्ट-क्लास फंक्शन्स वाली प्रोग्रामिंग लैंग्वेजेस में, फंक्शन्स को वेरिएबल्स में स्टोर किया जा सकता है और अन्य फंक्शन्स को आर्ग्युमेंट्स के रूप में पास किया जा सकता है। फंक्शन्स अन्य फंक्शन्स को अपने रिटर्न वैल्यू के रूप में भी उपयोग कर सकते हैं।
क्लोजर एक फर्स्ट-क्लास फंक्शन है जिसमें एक एनवायरनमेंट होता है। एनवायरनमेंट उन वेरिएबल्स का मैपिंग है जो क्लोजर बनाए जाने पर मौजूद थे। क्लोजर इन वेरिएबल्स तक अपनी पहुंच बनाए रखेगा, भले ही वे किसी अन्य स्कोप में परिभाषित हों।
रूबी में फर्स्ट-क्लास फंक्शन्स नहीं हैं, लेकिन इसमें ब्लॉक्स, प्रॉक्स और लैम्ब्डा के रूप में क्लोजर्स हैं। ब्लॉक्स का उपयोग मेथड्स को कोड के ब्लॉक्स पास करने के लिए किया जाता है, और प्रॉक्स और लैम्ब्डा कोड के ब्लॉक्स को वेरिएबल्स में स्टोर करने की अनुमति देते हैं।
ब्लॉक्स
रूबी में, ब्लॉक्स कोड के स्निपेट्स हैं जिन्हें बाद में निष्पादित करने के लिए बनाया जा सकता है। ब्लॉक्स को मेथड्स को पास किया जाता है जो उन्हें do और end कीवर्ड्स के भीतर यील्ड करते हैं। कई उदाहरणों में से एक #each मेथड है, जो एन्युमरेबल ऑब्जेक्ट्स पर लूप करता है।
[1,2,3].each do |n|
puts "#{n}!"
end
[1,2,3].each { |n| puts "#{n}!" } # वन-लाइन समकक्ष।
इस उदाहरण में, एक ब्लॉक को Array#each मेथड को पास किया जाता है, जो ऐरे में प्रत्येक आइटम के लिए ब्लॉक को चलाता है और इसे कंसोल पर प्रिंट करता है।
def each
i = 0
while i < size
yield at(i)
i += 1
end
end
Array#each के इस सरलीकृत उदाहरण में, while लूप में, पास किए गए ब्लॉक को ऐरे में प्रत्येक आइटम के लिए निष्पादित करने के लिए yield कहा जाता है। ध्यान दें कि इस मेथड में कोई आर्ग्युमेंट नहीं हैं, क्योंकि ब्लॉक को मेथड में इम्प्लिसिटली पास किया जाता है।
इम्प्लिसिट ब्लॉक्स और yield कीवर्ड
रूबी में, मेथड्स ब्लॉक्स को इम्प्लिसिटली और एक्सप्लिसिटली ले सकते हैं। इम्प्लिसिट ब्लॉक पासिंग मेथड में yield कीवर्ड को कॉल करके काम करती है। yield कीवर्ड विशेष है। यह पास किए गए ब्लॉक को ढूंढता है और कॉल करता है, इसलिए आपको मेथड द्वारा स्वीकार किए जाने वाले आर्ग्युमेंट्स की सूची में ब्लॉक को जोड़ने की आवश्यकता नहीं है।
क्योंकि रूबी इम्प्लिसिट ब्लॉक पासिंग की अनुमति देती है, आप सभी मेथड्स को ब्लॉक के साथ कॉल कर सकते हैं। अगर यह yield नहीं कहता, तो ब्लॉक को नजरअंदाज कर दिया जाता है।
irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]
अगर कॉल किया गया मेथड यील्ड करता है, तो पास किया गया ब्लॉक ढूंढा जाता है और yield कीवर्ड को पास किए गए किसी भी आर्ग्युमेंट्स के साथ कहा जाता है।
def each
return to_enum(:each) unless block_given?
i = 0
while i < size
yield at(i)
i += 1
end
end
यह उदाहरण Enumerator का एक इंस्टेंस लौटाता है जब तक कि ब्लॉक न दिया जाए।
yield और block_given? कीवर्ड्स करंट स्कोप में ब्लॉक को ढूंढते हैं। यह ब्लॉक्स को इम्प्लिसिटली पास करने की अनुमति देता है, लेकिन कोड को सीधे ब्लॉक तक पहुंचने से रोकता है क्योंकि यह वेरिएबल में स्टोर नहीं होता।
ब्लॉक्स को एक्सप्लिसिटली पास करना
हम एक एम्परसैंड पैरामीटर (आमतौर पर &block कहा जाता है) का उपयोग करके इसे आर्ग्युमेंट के रूप में जोड़कर एक मेथड में ब्लॉक को एक्सप्लिसिटली स्वीकार कर सकते हैं। चूंकि ब्लॉक अब एक्सप्लिसिट है, हम परिणामी ऑब्जेक्ट पर सीधे #call मेथड का उपयोग कर सकते हैं yield पर भरोसा करने के बजाय।
&block आर्ग्युमेंट एक उचित आर्ग्युमेंट नहीं है, इसलिए इस मेथड को ब्लॉक के अलावा किसी भी चीज़ के साथ कॉल करने से ArgumentError उत्पन्न होगा।
def each_explicit(&block)
return to_enum(:each) unless block
i = 0
while i < size
block.call at(i)
i += 1
end
end
जब एक ब्लॉक को इस तरह पास किया जाता है और वेरिएबल में स्टोर किया जाता है, तो यह स्वचालित रूप से एक प्रॉक में बदल जाता है।
प्रॉक्स
एक "प्रॉक" Proc क्लास का एक इंस्टेंस है, जो निष्पादित किए जाने वाले कोड ब्लॉक को रखता है, और इसे वेरिएबल में स्टोर किया जा सकता है। प्रॉक बनाने के लिए, आप Proc.new को कॉल करते हैं और इसे एक ब्लॉक पास करते हैं।
proc = Proc.new { |n| puts "#{n}!" }
चूंकि एक प्रॉक को वेरिएबल में स्टोर किया जा सकता है, इसे सामान्य आर्ग्युमेंट की तरह मेथड को भी पास किया जा सकता है। उस स्थिति में, हम एम्परसैंड का उपयोग नहीं करते, क्योंकि प्रॉक को एक्सप्लिसिटली पास किया जाता है।
def run_proc_with_random_number(proc)
proc.call(random)
end
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)
प्रॉक बनाने और उसे मेथड में पास करने के बजाय, आप रूबी के एम्परसैंड पैरामीटर सिंटैक्स का उपयोग कर सकते हैं जिसे हमने पहले देखा था और ब्लॉक का उपयोग कर सकते हैं।
def run_proc_with_random_number(&proc)
proc.call(random)
end
run_proc_with_random_number { |n| puts "#{n}!" }
मेथड में आर्ग्युमेंट में जोड़े गए एम्परसैंड पर ध्यान दें। यह पास किए गए ब्लॉक को प्रॉक ऑब्जेक्ट में बदल देगा और इसे मेथड स्कोप में वेरिएबल में स्टोर करेगा।
टिप: हालांकि कुछ स्थितियों में मेथड में प्रॉक होना उपयोगी होता है, ब्लॉक से प्रॉक में रूपांतरण एक प्रदर्शन हिट पैदा करता है। जब भी संभव हो, इम्प्लिसिट ब्लॉक्स का उपयोग करें।
#to_proc
सिम्बल्स, हैश और मेथड्स को उनके #to_proc मेथड्स का उपयोग करके प्रॉक्स में बदला जा सकता है। इसका अक्सर देखा जाने वाला उपयोग एक सिम्बल से बने प्रॉक को मेथड में पास करना है।
[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }
यह उदाहरण ऐरे के प्रत्येक एलिमेंट पर #to_s कॉल करने के तीन समकक्ष तरीके दिखाता है। पहले में, एम्परसैंड से पहले एक सिम्बल पास किया जाता है, जो स्वचालित रूप से इसके #to_proc मेथड को कॉल करके इसे प्रॉक में बदल देता है। अंतिम दो दिखाते हैं कि वह प्रॉक कैसा दिख सकता है।
class Symbol
def to_proc
Proc.new { |i| i.send(self) }
end
end
हालांकि यह एक सरलीकृत उदाहरण है, Symbol#to_proc का कार्यान्वयन दिखाता है कि हुड के नीचे क्या हो रहा है। मेथड एक प्रॉक लौटाता है जो एक आर्ग्युमेंट लेता है और उसे self भेजता है। चूंकि इस संदर्भ में self सिम्बल है, यह Integer#to_s मेथड को कॉल करता है।
लैम्ब्डा
लैम्ब्डा अनिवार्य रूप से कुछ विशिष्ट कारकों के साथ प्रॉक्स हैं। वे दो मायनों में "रेगुलर" मेथड्स की तरह अधिक हैं: वे कॉल किए जाने पर पास किए गए आर्ग्युमेंट्स की संख्या को लागू करते हैं और वे "सामान्य" रिटर्न का उपयोग करते हैं।
जब एक लैम्ब्डा को कॉल किया जाता है जो एक आर्ग्युमेंट की उम्मीद करता है बिना एक के, या यदि आप एक आर्ग्युमेंट को एक लैम्ब्डा में पास करते हैं जो इसकी उम्मीद नहीं करता, तो रूबी ArgumentError उठाती है।
irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
from (irb):8:in `block in irb_binding'
from (irb):8
from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `<main>'
साथ ही, लैम्ब्डा रिटर्न कीवर्ड को उसी तरह मानता है जैसे एक मेथड करता है। जब एक प्रॉक को कॉल किया जाता है, तो प्रोग्राम प्रॉक में कोड ब्लॉक को कंट्रोल देता है। इसलिए, अगर प्रॉक रिटर्न करता है, तो करंट स्कोप रिटर्न करता है। अगर एक प्रॉक को एक फंक्शन के अंदर कॉल किया जाता है और return कॉल करता है, तो फंक्शन तुरंत रिटर्न भी करता है।
def return_from_proc
a = Proc.new { return 10 }.call
puts "This will never be printed."
end
यह फंक्शन प्रॉक को कंट्रोल देगा, इसलिए जब यह रिटर्न करता है, तो फंक्शन रिटर्न करता है। इस उदाहरण में फंक्शन को कॉल करने से आउटपुट कभी प्रिंट नहीं होगा और 10 रिटर्न करेगा।
def return_from_lambda
a = lambda { return 10 }.call
puts "The lambda returned #{a}, and this will be printed."
end
लैम्ब्डा का उपयोग करते समय, यह प्रिंट होगा। लैम्ब्डा में return कॉल करना मेथड में return कॉल करने जैसा व्यवहार करेगा, इसलिए a वेरिएबल 10 से भर जाता है और लाइन कंसोल पर प्रिंट होती है।
ब्लॉक्स, प्रॉक्स और लैम्ब्डा
अब जब हम ब्लॉक्स, प्रॉक्स और लैम्ब्डा दोनों में पूरी तरह से चले गए हैं, तो आइए पीछे हटें और तुलना को सारांशित करें।
- ब्लॉक्स का उपयोग रूबी में फंक्शन्स को कोड के बिट्स पास करने के लिए बड़े पैमाने पर किया जाता है।
yieldकीवर्ड का उपयोग करके, एक ब्लॉक को प्रॉक में बदले बिना इम्प्लिसिटली पास किया जा सकता है। - एम्परसैंड से पहले पैरामीटर्स का उपयोग करते समय, मेथड को ब्लॉक पास करने से मेथड के संदर्भ में एक प्रॉक परिणाम होता है। प्रॉक्स ब्लॉक्स की तरह व्यवहार करते हैं, लेकिन उन्हें वेरिएबल में स्टोर किया जा सकता है।
- लैम्ब्डा प्रॉक्स हैं जो मेथड्स की तरह व्यवहार करते हैं, अर्थात वे अरिटी को लागू करते हैं और अपने पैरेंट स्कोप के बजाय मेथड्स की तरह रिटर्न करते हैं।
यह रूबी में क्लोजर्स पर हमारी नज़र का समापन करता है। क्लोजर्स के बारे में सीखने के लिए और भी बहुत कुछ है जैसे लेक्सिकल स्कोप्स और बाइंडिंग्स, लेकिन हम इसे भविष्य के एपिसोड के लिए रखेंगे। इस बीच, कृपया हमें बताएं कि आप रूबी मैजिक के भविष्य के इंस्टॉलमेंट में क्या पढ़ना चाहेंगे, क्लोजर्स या अन्यथा @AppSignal पर।