नया साल मुबारक, और रूबी मैजिक में वापस स्वागत है! इस विंटर एपिसोड में, हम बाइंडिंग्स और स्कोप्स में गोता लगाएंगे। तो अपने स्कीज़ पहनें और हमें जंगल में गहराई तक फॉलो करें।
पिछली बार, हमने ब्लॉक्स, प्रॉक्स और लैम्ब्डास की तुलना करके रूबी में क्लोजर्स को देखा था। तीन प्रकारों के बीच अंतरों के अलावा, हमने इस बात पर चर्चा की कि क्लोजर को क्या परिभाषित करता है।
एक क्लोजर एक फर्स्ट-क्लास फ़ंक्शन है जिसमें एक वातावरण होता है। वातावरण उन वेरिएबल्स का मैपिंग है जो क्लोजर बनाए जाने पर मौजूद थे। क्लोजर इन वेरिएबल्स तक अपनी पहुंच बरकरार रखता है, भले ही वे किसी अन्य स्कोप में परिभाषित हों।
हमने रूबी के फर्स्ट-क्लास फ़ंक्शन्स के समकक्ष का पता लगाया, लेकिन हमने सुविधाजनक रूप से वातावरण को छोड़ दिया। इस एपिसोड में, हम देखेंगे कि क्लोजर्स, क्लासेस और क्लास इंस्टेंसेस के लिए वह वातावरण कैसे काम करता है, यह जांच करके कि रूबी अपने बाइंडिंग्स के माध्यम से लेक्सिकल स्कोप को कैसे संभालती है।
लेक्सिकल स्कोप
प्रोग्रामिंग में, स्कोप कोड के एक विशिष्ट भाग पर उपलब्ध बाइंडिंग्स को संदर्भित करता है। एक बाइंडिंग, या नाम बाइंडिंग, एक नाम को मेमोरी रेफरेंस से बांधता है, जैसे एक वेरिएबल के नाम को उसके मान से। स्कोप परिभाषित करता है कि self का क्या अर्थ है, कौन सी विधियाँ कॉल की जा सकती हैं, और कौन से वेरिएबल्स उपलब्ध हैं।
रूबी, अधिकांश आधुनिक प्रोग्रामिंग भाषाओं की तरह, एक स्टैटिक स्कोप का उपयोग करती है, जिसे अक्सर लेक्सिकल स्कोप कहा जाता है (डायनामिक स्कोप के विपरीत)। वर्तमान स्कोप कोड की संरचना पर आधारित है और कोड के विशिष्ट भागों पर उपलब्ध वेरिएबल्स को निर्धारित करता है। इसका मतलब है कि स्कोप तब बदलता है जब कोड विधियों, ब्लॉक्स और क्लासेस के बीच कूदता है — क्योंकि उन सभी के अलग-अलग स्थानीय वेरिएबल्स हो सकते हैं, उदाहरण के लिए।
def bar foo = 1 foo end bar # => 1इस विधि में, हम एक विधि के अंदर एक स्थानीय वेरिएबल बनाते हैं और इसे कंसोल पर प्रिंट करते हैं। वेरिएबल विधि के अंदर इन स्कोप है, क्योंकि इसे वहीं बनाया गया है।
foo = 1 def bar foo end bar # => NameError (undefined local variable or method `foo' for main:Object)इस उदाहरण में, हम वेरिएबल को विधि के बाहर बनाते हैं। जब हम वेरिएबल को एक विधि के अंदर कॉल करते हैं, तो हमें एक त्रुटि मिलती है, क्योंकि वेरिएबल आउट ऑफ स्कोप है। स्थानीय वेरिएबल्स कसकर स्कोप्ड होते हैं, जिसका अर्थ है कि एक विधि अपने बाहर परिभाषित वेरिएबल तक तब तक नहीं पहुंच सकती जब तक कि इसे आर्गुमेंट के रूप में पास नहीं किया जाता।
@foo = 1 def bar @foo end bar # => 1जहां स्थानीय वेरिएबल्स स्थानीय रूप से उपलब्ध हैं, इंस्टेंस वेरिएबल्स एक क्लास इंस्टेंस की सभी विधियों के लिए उपलब्ध हैं।
विरासत में मिले स्कोप और Proc बाइंडिंग्स
जैसा कि हमने पिछले उदाहरणों में देखा है, स्कोप कोड में स्थान पर आधारित है। एक विधि के बाहर परिभाषित एक स्थानीय वेरिएबल विधि के अंदर स्कोप में नहीं है लेकिन इसे इंस्टेंस वेरिएबल में बदलकर उपलब्ध कराया जा सकता है। विधियाँ अपने बाहर परिभाषित स्थानीय वेरिएबल्स तक नहीं पहुंच सकतीं क्योंकि विधियों का अपना स्कोप होता है, उनकी अपनी बाइंडिंग्स के साथ।
प्रॉक्स (एक्सटेंशन द्वारा ब्लॉक्स और लैम्ब्डास सहित) अलग हैं। जब भी एक प्रॉक इंस्टेंटिएट होता है, एक बाइंडिंग बनाई जाती है जो उस संदर्भ में स्थानीय वेरिएबल्स के रेफरेंस को विरासत में लेती है जहां ब्लॉक बनाया गया था।
foo = 1 Proc.new { foo }.call # => 1इस उदाहरण में, हम foo नामक एक वेरिएबल को 1 पर सेट करते हैं। आंतरिक रूप से, दूसरी पंक्ति पर बनाया गया Proc ऑब्जेक्ट एक नई बाइंडिंग बनाता है। प्रॉक को कॉल करते समय, हम वेरिएबल के मान के लिए पूछ सकते हैं।
चूंकि बाइंडिंग तब बनाई जाती है जब प्रॉक इंस्टेंटिएट होता है, हम प्रॉक को वेरिएबल को परिभाषित करने से पहले नहीं बना सकते, भले ही ब्लॉक को वेरिएबल परिभाषित होने के बाद तक कॉल नहीं किया जाता है।
proc = Proc.new { foo } foo = 1 proc.call # => NameError (undefined local variable or method `foo' for main:Object)प्रॉक को कॉल करने से एक NameError उत्पन्न होगा क्योंकि वेरिएबल प्रॉक की बाइंडिंग्स में परिभाषित नहीं है। इस प्रकार, एक प्रॉक में एक्सेस किए गए किसी भी वेरिएबल को प्रॉक बनाए जाने से पहले परिभाषित किया जाना चाहिए या आर्गुमेंट के रूप में पास किया जाना चाहिए।
foo = 1 proc = Proc.new { foo } foo = 2 proc.call # => 2हालांकि, हम इसे मुख्य संदर्भ में परिभाषित किए जाने के बाद वेरिएबल को बदल सकते हैं क्योंकि प्रॉक की बाइंडिंग इसकी कॉपी करने के बजाय उस पर एक रेफरेंस रखती है।
foo = 1 Proc.new { foo = 2 }.call foo #=> 2इस उदाहरण में, हम देख सकते हैं कि foo वेरिएबल प्रॉक में उतना ही ऑब्जेक्ट की ओर इंगित करता है जितना इसके बाहर। हम इसे प्रॉक के अंदर अपडेट कर सकते हैं ताकि इसके बाहर का वेरिएबल भी अपडेट हो जाए।
बाइंडिंग्स
वर्तमान स्कोप का ट्रैक रखने के लिए, रूबी बाइंडिंग्स का उपयोग करती है, जो कोड में प्रत्येक स्थिति पर निष्पादन संदर्भ को एनकैप्सुलेट करती हैं। binding विधि एक Binding ऑब्जेक्ट लौटाती है जो वर्तमान स्थिति पर बाइंडिंग्स का वर्णन करती है।
foo = 1 binding.local_variables # => [:foo]बाइंडिंग ऑब्जेक्ट में #local_variables नामक एक विधि होती है जो वर्तमान स्कोप में उपलब्ध सभी स्थानीय वेरिएबल्स के नाम लौटाती है।
foo = 1 binding.eval("foo") # => 1बाइंडिंग पर #eval विधि का उपयोग करके कोड का मूल्यांकन किया जा सकता है। उपरोक्त उदाहरण बहुत उपयोगी नहीं है, क्योंकि केवल foo को कॉल करने का वही परिणाम होगा। हालांकि, चूंकि एक बाइंडिंग एक ऑब्जेक्ट है जिसे चारों ओर पास किया जा सकता है, इसका उपयोग कुछ अधिक दिलचस्प चीजों के लिए किया जा सकता है। आइए एक उदाहरण देखें।
एक वास्तविक जीवन उदाहरण
अब जब हमने अपने गैरेज की सुरक्षा में बाइंडिंग्स के बारे में सीखा है, तो उन्हें ढलानों पर ले जाएं और बर्फ में खेलें। रूबी के भाषा में बाइंडिंग्स के आंतरिक उपयोग के अलावा, कुछ स्थितियां हैं जहां बाइंडिंग ऑब्जेक्ट्स का स्पष्ट रूप से उपयोग किया जाता है। एक अच्छा उदाहरण ERB है — रूबी की टेम्पलेटिंग प्रणाली।
require 'erb' x = 1 def y 2 end template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`"इस उदाहरण में, हम x नामक एक वेरिएबल, y नामक एक विधि, और एक ERB टेम्पलेट बनाते हैं जो दोनों को संदर्भित करता है। हम फिर वर्तमान बाइंडिंग को ERB#result में पास करते हैं, जो टेम्पलेट में ERB टैग्स का मूल्यांकन करता है और वेरिएबल्स से भरा एक स्ट्रिंग लौटाता है।
हुड के तहत, ERB पास की गई बाइंडिंग के स्कोप में प्रत्येक ERB टैग के कंटेंट का मूल्यांकन करने के लिए Binding#eval का उपयोग करता है। एक सरलीकृत कार्यान्वयन जो उपरोक्त उदाहरण के लिए काम करता है वह इस तरह दिख सकता है:
class DiyErb def initialize(template) @template = template end def result(binding) @template.gsub(/<%=(.+?)%>/) do binding.eval($1) end end end x = 1 def y 2 end template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`"DiyErb क्लास इनिशियलाइज़ेशन पर एक टेम्पलेट स्ट्रिंग लेती है। इसकी #result विधि सभी ERB टैग्स को ढूंढती है और उन्हें उनके कंटेंट का मूल्यांकन करने के परिणाम से बदल देती है। ऐसा करने के लिए, यह पास की गई बाइंडिंग पर Binding#eval को कॉल करती है, ERB टैग्स के कंटेंट के साथ।
वर्तमान बाइंडिंग को #result विधि को कॉल करते समय पास करके, eval कॉल्स विधि के बाहर परिभाषित वेरिएबल्स को एक्सेस कर सकते हैं, और यहां तक कि क्लास के बाहर भी, बिना उन्हें स्पष्ट रूप से पास किए।
क्या हम आपको जंगल में खो गए?
हम आशा करते हैं कि आपको हमारी जंगल में स्की यात्रा पसंद आई। हम स्कोप्स और क्लोजर्स में गहराई तक गए, उन्हें छोड़ने के बाद। हम आशा करते हैं कि हमने आपको जंगल में नहीं खोया है। कृपया हमें बताएं यदि आप बाइंडिंग्स के बारे में अधिक सीखना चाहेंगे, या कोई अन्य रूबी विषय जिसमें आप गोता लगाना चाहेंगे।
हमारा अनुसरण करने के लिए धन्यवाद और अगले डेवलपर के लिए छोड़ने से पहले अपने बाइंडिंग्स से बर्फ हटाएं। यदि आप इन जादुई यात्राओं को पसंद करते हैं, तो आप रूबी मैजिक की सदस्यता लेना पसंद कर सकते हैं ताकि जब हम लगभग एक महीने में एक नया लेख प्रकाशित करें तो एक ईमेल प्राप्त करें।