लंबे समय से प्रतीक्षित संस्करण 3.0.0 आखिरकार जारी कर दिया गया है। कई बेहतरीन सुधारों के साथ, जैसे पिछले संस्करण की तुलना में 3x तेज प्रदर्शन बूस्ट, समवर्ती-समानांतर प्रयोगात्मक सुविधाएं, आदि., Ruby टीम ने Ruby में डायनामिक टाइपिंग के लिए एक नई सिंटैक्स भाषा भी पेश की: RBS।
यह कुछ ऐसा था जिस पर टीम वर्षों से चर्चा कर रही थी, Sorbet जैसे स्टैटिक टाइप चेकिंग के लिए समुदाय-विकसित टूल की सफलता के आधार पर।
Sorbet Stripe द्वारा समर्थित एक शक्तिशाली टाइप चेकर है। यह आपके कोड को एनोटेट करके और/या RBI फ़ाइलों को परिभाषित करके जांचता है। RBI फ़ाइलें, बदले में, स्टैटिक और डायनामिक घटकों के बीच इंटरफेस के रूप में काम करती हैं जो उनका "विवरण" प्रदान करती हैं (कॉन्स्टेंट्स, पूर्वज, मेटाप्रोग्रामिंग कोड, और अधिक)।
तो, यदि Sorbet ज्यादातर स्टैटिक चेकिंग से संबंधित है और RBS को डायनामिक टाइपिंग को संबोधित करने के लिए बनाया गया था, तो उनके बीच क्या अंतर है? वे दोनों कैसे सह-अस्तित्व में रहेंगे? मुझे एक के बजाय दूसरे का उपयोग कब करना चाहिए?
ये RBS की प्रमुख भूमिका के बारे में काफी सामान्य प्रश्न हैं। इसलिए हमने यह टुकड़ा लिखने का फैसला किया। व्यवहार में स्पष्ट करने के लिए, आपको इसे अपनाने पर विचार क्यों करना चाहिए, इसकी क्षमता के आधार पर। आइए सीधे अंदर गोता लगाएं!
बुनियादी बातों से शुरू करना
आइए स्टैटिक टाइपिंग _और डायनामिक टाइपिंग_ के बीच अंतर की स्पष्ट समझ से शुरू करें। हालांकि यह बुनियादी है, यह RBS की भूमिका को समझने के लिए एक महत्वपूर्ण अवधारणा है।
आइए एक स्टैटिकली टाइप्ड भाषा से कोड स्निपेट लें एक संदर्भ के रूप में:
➜ String str = ""; str = 2.4;
यह कोई खबर नहीं है कि ऐसी भाषा अपने ऑब्जेक्ट्स और वेरिएबल्स के प्रकारों की परवाह करती है। उस ने कहा, ऊपर जैसा कोड एक त्रुटि फेंकेगा।
Ruby, कई अन्य भाषाओं की तरह जैसे JavaScript, Python, और Objective-C, आपके ऑब्जेक्ट्स के लिए आप किन प्रकारों को लक्षित कर रहे हैं, उस पर इतना ध्यान नहीं देता।
Ruby में वही कोड बस सफलतापूर्वक चलेगा, जैसा कि नीचे देखा गया है:
➜ irb str = "" str = 2.4 puts str # prints 2.4
यह संभव है क्योंकि Ruby का इंटरप्रेटर जानता है कि एक प्रकार से दूसरे प्रकार में डायनामिकली कैसे स्विच किया जाए।
हालांकि, इंटरप्रेटर क्या अनुमति देता है इसकी एक सीमा है। उदाहरण के लिए, निम्न कोड परिवर्तन लें:
➜ irb val = "6.0" result = val + 2.0 puts result
यह बदले में निम्न त्रुटि उत्पन्न करेगा:
त्रुटि: Float का String में कोई निहित रूपांतरण नहीं
उदाहरण के लिए, वही कोड JavaScript के साथ चलाने पर ठीक चलेगा।
कहानी का नैतिक: Ruby वास्तव में प्रकारों को डायनामिकली इन्फर करता है; लेकिन, अन्य प्रमुख डायनामिक भाषाओं के विपरीत, यह सब कुछ स्वीकार नहीं करेगा। इसके बारे में जागरूक रहें!
और यहीं टाइप चेकर्स (चाहे स्टैटिक या डायनामिक) उपयोगी हो जाते हैं।
RBS बनाम Sorbet
सही, मुझे आपके डायनामिक बनाम स्टैटिक चीज़ के बारे में बात समझ आ गई। लेकिन, Sorbet के बारे में क्या? क्या यह बहिष्कृत हो जाएगा?
बिल्कुल नहीं। RBS और Sorbet के बीच प्राथमिक (और शायद सबसे महत्वपूर्ण) अंतर यह है कि पूर्व सिर्फ एक भाषा है, जबकि बाद वाला एक पूर्ण टाइप चेकर है।
Ruby टीम RBS के मुख्य लक्ष्य के रूप में दावा करती है कि यह आपके कोड की संरचना का वर्णन करे। यह टाइप चेकिंग नहीं करेगा, बल्कि उस संरचना को परिभाषित करेगा जिसका उपयोग टाइप चेकर्स (जैसे Sorbet या कोई अन्य) टाइप चेक करने के लिए कर सकते हैं। कोड संरचना एक नए फ़ाइल एक्सटेंशन — .rbs के भीतर संग्रहीत की जाती है।
इसे देखने के लिए, आइए निम्न Ruby क्लास को उदाहरण के रूप में लें:
class Super def initialize(val) @val = val end def val? @val end end class Test < Super def initialize(val, flag) super(val) @flag = flag end def flag? @flag end end
यह Ruby में एक सरल इनहेरिटेंस को दर्शाता है। यहां ध्यान देने वाली दिलचस्प बात यह है कि हम क्लास में उपयोग किए गए प्रत्येक एट्रिब्यूट के प्रकारों का अनुमान नहीं लगा सकते, flag को छोड़कर।
चूंकि flag डिफ़ॉल्ट मान के साथ इनिशियलाइज़ होता है, डेवलपर और टाइप चेकर दोनों आगे के दुरुपयोग को रोकने के लिए प्रकार का अनुमान लगा सकते हैं।
उपरोक्त क्लास का RBS फॉर्मेट में उचित प्रतिनिधित्व निम्न होगा:
class Super attr_reader val : untyped def initialize : (val: untyped) -> void end class Test < Super attr_reader flag : bool def initialize : (val: untyped, ?flag: bool) -> void def flag? : () -> bool end
इसे पचाने के लिए कुछ समय लें। यह एक घोषणात्मक भाषा है, इसलिए केवल सिग्नेचर RBS फ़ाइल में दिखाई दे सकते हैं। सरल, है ना?
चाहे वह CLI टूल (बाद में उस पर अधिक) द्वारा ऑटोजेनरेट किया गया हो या आपके द्वारा, जब इसका अनुमान नहीं लगाया जा सकता तो untyped के रूप में प्रकार को एनोटेट करना सुरक्षित है।
यदि आप val के प्रकार के बारे में निश्चित हैं, उदाहरण के लिए, आपका RBS मैपिंग निम्न पर स्विच किया जा सकता है:
class Super attr_reader val : Integer def initialize : (val: Integer) -> void end
यह नोट करना भी महत्वपूर्ण है कि RBS के निर्माण और सुधार की दिशा में Ruby और Sorbet दोनों टीमें काम कर रही थीं (और अभी भी हैं)। Sorbet टीम का वर्षों से टाइप चेकिंग के साथ अनुभव इस प्रोजेक्ट के साथ बहुत सी चीजों को ठीक करने में Ruby टीम की मदद करता था।
RBS और RBI फ़ाइलों के बीच अंतरसंचालनीयता अभी भी विकास के अधीन है। लक्ष्य Sorbet और किसी अन्य चेकर टूल के लिए एक आधिकारिक और केंद्रीकृत आधार होना है जिसका पालन किया जा सके।
RBS CLI टूल
Ruby टीम के पास RBS विकसित करते समय एक महत्वपूर्ण विचार था एक CLI टूल शिप करना जो डेवलपर्स को इसे आज़माने और इसका उपयोग करना सीखने में मदद कर सके। इसे rbs कहा जाता है और Ruby 3 के साथ डिफ़ॉल्ट रूप से आता है। यदि आपने अभी तक अपना Ruby संस्करण अपग्रेड नहीं किया है, तो आप इसके जेम को सीधे अपने प्रोजेक्ट में भी जोड़ सकते हैं:
➜ gem install rbs
rbs help कमांड कमांड उपयोग के साथ उपलब्ध कमांड दिखाएगा।
उपलब्ध कमांड की सूची
इनमें से अधिकांश कमांड Ruby कोड संरचनाओं को पार्स और विश्लेषण करने पर केंद्रित हैं। उदाहरण के लिए, कमांड ancestors दी गई क्लास की पदानुक्रमित संरचना को स्वीप करता है ताकि उसके पूर्वजों की जांच की जा सके:
➜ rbs ancestors ::String ::String ::Comparable ::Object ::Kernel ::BasicObject
methods कमांड दी गई क्लास की सभी मेथड संरचनाओं को प्रदर्शित करता है:
➜ rbs methods ::String ! (public) != (public) !~ (public) ... Array (private) Complex (private) Float (private) ... autoload? (private) b (public) between? (public) ...
किसी विशिष्ट मेथड संरचना को देखना चाहते हैं? method के लिए जाएं:
➜ rbs method ::String split ::String#split defined_in: ::String implementation: ::String accessibility: public types: (?::Regexp | ::string pattern, ?::int limit) -> ::Array[::String] | (?::Regexp | ::string pattern, ?::int limit) { (::String) -> void } -> self
आज RBS के साथ शुरू करने वालों के लिए, कमांड prototype मौजूदा क्लास के लिए प्रकारों को स्कैफोल्डिंग में बहुत मदद कर सकता है। कमांड RBS फ़ाइलों के प्रोटोटाइप उत्पन्न करता है।
आइए पिछले Test < Super इनहेरिटेंस उदाहरण को लें और कोड को appsignal.rb नामक फ़ाइल में सहेजें। फिर, निम्न कमांड चलाएं:
➜ rbs prototype rb appsignal.rb
चूंकि कमांड rb, rbi, और runtime जनरेटर की अनुमति देता है, आपको prototype कमांड के ठीक बाद आपके द्वारा स्कैफोल्ड की जा रही फ़ाइल का विशिष्ट प्रकार प्रदान करना होगा, उसके बाद फ़ाइल पथनाम।
निष्पादन का निम्न परिणाम है:
class Super def initialize: (untyped val) -> untyped def val?: () -> untyped end class Test < Super def initialize: (untyped val, ?flag: bool flag) -> untyped def flag?: () -> untyped end
हमारे पहले RBS संस्करण के काफी समान। जैसा कि पहले उल्लेख किया गया है, टूल किसी भी प्रकार को untyped के रूप में चिह्नित करता है जिसका अनुमान नहीं लगाया जा सका।
यह मेथड रिटर्न के लिए भी मायने रखता है। flag परिभाषा के रिटर्न प्रकार पर ध्यान दें। एक डेवलपर के रूप में, आप शायद निश्चित हैं कि मेथड हमेशा एक बूलियन लौटाता है, लेकिन Ruby की डायनामिक प्रकृति के कारण, टूल 100% यह कहने में असमर्थ है कि यह ऐसा है।
और यहीं एक और Ruby 3 का बच्चा बचाव के लिए आता है: TypeProf।
TypeProf टूल
TypeProf Ruby के लिए एक टाइप विश्लेषण टूल है जिसे कुछ सिंटैक्स ट्री व्याख्या के शीर्ष पर बनाया गया है।
अभी भी प्रयोगात्मक होने के बावजूद, यह आपके कोड को क्या करने की कोशिश कर रहा है, इसे समझने में बहुत शक्तिशाली साबित हुआ है।
यदि आपके पास Ruby 3 नहीं है, तो बस अपने प्रोजेक्ट में जेम जोड़ें:
➜ gem install typeprof
अब, आइए उसी appsignal.rb फ़ाइल को इसके खिलाफ चलाएं:
➜ typeprof appsignal.rb
यह आउटपुट है:
# Classes class Super @val: untyped def initialize: (untyped) -> untyped def val?: -> untyped end class Test < Super @val: untyped @flag: true def initialize: (untyped, ?flag: true) -> true def flag?: -> true end
ध्यान दें कि flag अब कैसे मैप किया गया है। यह केवल संभव है क्योंकि, RBS प्रोटोटाइप के विपरीत, TypeProf मेथड के बॉडी को स्कैन करता है यह समझने की कोशिश करता है कि उस विशिष्ट वेरिएबल पर क्या क्रियाएं की जा रही हैं। चूंकि यह इस वेरिएबल में किसी भी प्रत्यक्ष परिवर्तन की पहचान नहीं कर सका, TypeProf ने मेथड रिटर्न को बूलियन के रूप में सुरक्षित रूप से मैप किया।
उदाहरण के लिए, मान लें कि TypeProf के पास अन्य क्लास तक पहुंच होगी जो Test क्लास को इंस्टेंटिएट और उपयोग करती हैं। इसके साथ, यह आपके कोड में और भी गहराई तक जा सकता है और अपनी भविष्यवाणियों को ठीक कर सकता है। मान लें कि निम्न कोड स्निपेट appsignal.rb फ़ाइल के अंत में जोड़ा गया है:
testSub = Test.new("My value", "My value" == "") testSup = Super.new("My value")
और आपने initialize मेथड सिग्नेचर को निम्न में बदल दिया:
def initialize(val, flag)
जब आप कमांड को फिर से चलाते हैं, तो यह आउटपुट होना चाहिए:
# Classes class Super @val: String def initialize: (String) -> String def val?: -> String end class Test < Super @val: String @flag: bool def initialize: (String val, bool flag) -> bool def flag?: -> bool end
सुपर कूल!
TypeProf इनहेरिटेड एट्रिब्यूट्स के साथ बहुत अच्छी तरह से नहीं निपट सकता। इसलिए हम एक नया Super ऑब्जेक्ट इंस्टेंटिएट कर रहे हैं। अन्यथा, यह नहीं समझ पाएगा कि val एक String है।
TypeProf का प्रमुख समर्थक इसकी सुरक्षा है। जब भी यह कुछ निश्चित रूप से समझने में असमर्थ होता है, तो untyped वापस कर दिया जाएगा।
आंशिक RBS विनिर्देश
आधिकारिक डॉक्स से एक महत्वपूर्ण चेतावनी बताती है कि, हालांकि TypeProf बहुत शक्तिशाली है, आपको इसकी सीमाओं के बारे में पता होना चाहिए कि यह RBS कोड के संदर्भ में क्या उत्पन्न कर सकता है और क्या नहीं।
उदाहरण के लिए, Ruby डेवलपर्स के बीच एक सामान्य अभ्यास मेथड ओवरलोडिंग है जिसमें आप इसके आर्गुमेंट्स के आधार पर मेथड के विभिन्न व्यवहार को लागू करते हैं।
मान लें कि Super क्लास में एक नया मेथड spell जोड़ा गया है, जो पैरामीटर प्रकार के आधार पर Integer या String लौटाता है:
def spell(val) if val.is_a?(String) "" else 0 end end
RBS यूनियन टाइप (एक मान जो कई संभावित प्रकारों का प्रतिनिधित्व करता है) सिंटैक्स के माध्यम से इससे निपटने की अनुमति देकर इस अभ्यास को अपनाता है:
def spell: (String) -> String | (Integer) -> Integer
TypeProf केवल मेथड के बॉडी का विश्लेषण करके इसका अनुमान नहीं लगा सकता। इसकी मदद करने के लिए, आप अपने RBS फ़ाइल में मैन्युअल रूप से ऐसी परिभाषा जोड़ सकते हैं और TypeProf हमेशा वहां पहले निर्देशों के लिए जांच करेगा।
इसके लिए, आपको कमांड के अंत में RBS फ़ाइल पथ जोड़ना होगा:
typeprof appsignal.rb appsignal.rbs
नीचे नया आउटपुट देखें:
class Super ... def spell: (untyped val) -> (Integer | String) end
साथ ही, हम रनटाइम के दौरान Kernel#p के माध्यम से वास्तविक प्रकारों को भी सत्यापित कर सकते हैं यह टेस्ट करने के लिए कि ओवरलोडिंग काम कर रहा है या नहीं, appsignal.rb फ़ाइल के अंत में अगली दो पंक्तियों को जोड़कर:
p testSup.spell(42) p testSup.spell("str")
यह आउटपुट होना चाहिए:
# प्रकट प्रकार # appsignal.rb:11 #=> Integer # appsignal.rb:12 #=> String ...
अधिक जानकारी के लिए आधिकारिक डॉक्स देखना सुनिश्चित करें, विशेष रूप से TypeProf सीमाओं से संबंधित अनुभाग।
डक टाइपिंग
आपने इसके बारे में पहले सुना है। यदि एक Ruby ऑब्जेक्ट वह सब कुछ करता है जो एक बत्तख करता है, तो वह एक बत्तख है।
जैसा कि हमने देखा, Ruby इस बात की परवाह नहीं करता कि आपके ऑब्जेक्ट्स क्या होने वाले हैं। प्रकार डायनामिकली बदल सकते हैं और साथ ही ऑब्जेक्ट रेफरेंस भी।
हालांकि उपयोगी, डक टाइपिंग मुश्किल हो सकती है। आइए एक उदाहरण देखें।
मान लें कि अब से, आपके द्वारा Super क्लास के लिए घोषित val एट्रिब्यूट, जो एक String है, हमेशा एक पूर्णांक में परिवर्तनीय होना चाहिए।
डेवलपर्स पर भरोसा करने के बजाय कि वे हमेशा रूपांतरण की गारंटी देंगे (शायद, अन्यथा त्रुटि फेंक दें), आप एक इंटरफ़ेस बना सकते हैं जिसमें कहा गया है कि:
interface _IntegerConvertible def to_int: () -> Integer end
इंटरफ़ेस प्रकार एक या अधिक मेथड प्रदान करते हैं जो ठोस क्लास और मॉड्यूल से अलग होते हैं। इस तरह, जब आप किसी निश्चित प्रकार को Super इंस्टेंटिएशन पर पास करना चाहते हैं, तो आप बस निम्न कर सकते हैं:
class Super attr_reader val : _IntegerConvertible def initialize : (val: _IntegerConvertible) -> void end
वह ठोस क्लास या मॉड्यूल जो इस इंटरफ़ेस को लागू करता है, उसे उचित सत्यापन सुनिश्चित करना होगा।
मेटाप्रोग्रामिंग
शायद Ruby की सबसे डायनामिक विशेषताओं में से एक रनटाइम के दौरान स्वयं द्वारा कोड बनाने वाले कोड बनाने की क्षमता है। वह मेटाप्रोग्रामिंग है।
चीजों की अनिश्चित प्रकृति के कारण, RBS CLI टूल मेटाप्रोग्रामिंग कोड से RBS उत्पन्न करने में सक्षम नहीं है।
आइए निम्न स्निपेट को उदाहरण के रूप में लें:
class Test define_method :multiply do |*args| args.inject(1, :*) end end p Test.new.multiply(2, 3, 5)
यह क्लास रनटाइम पर multiply नामक एक मेथड को परिभाषित करती है और इसे आर्गुमेंट्स को इंजेक्ट करने और प्रत्येक को पिछले परिणाम के साथ गुणा करने का निर्देश देती है।
एक बार जब आप RBS prototype कमांड चलाते हैं, तो यह आउटपुट होना चाहिए:
class Test end
आपके मेटाप्रोग्रामिंग कोड की जटिलता के आधार पर, TypeProf अभी भी इससे कुछ निकालने की पूरी कोशिश करेगा। लेकिन यह हमेशा गारंटी नहीं है।
याद रखें, आप हमेशा अपने RBS फ़ाइल में अपने प्रकार मैपिंग जोड़ सकते हैं और TypeProf उनका अग्रिम में पालन करेगा। यह मेटाप्रोग्रामिंग के लिए भी मान्य है।
नवीनतम रेपो परिवर्तनों के साथ अपडेट रहना भी महत्वपूर्ण है क्योंकि टीम लगातार नई सुविधाएं जारी कर रही है, जिसमें मेटाप्रोग्रामिंग पर अपडेट शामिल हो सकते हैं।
उस ने कहा, यदि आपके कोडबेस में किसी प्रकार का मेटाप्रोग्रामिंग शामिल है, तो इन टूल्स के साथ सावधान रहें। उनका अंधाधुंध उपयोग न करें!
समेटना
हमने अब तक जो चर्चा की है, उसके बारे में बहुत अधिक विवरण हैं, साथ ही RBS और TypeProf दोनों के लिए एज उपयोग के मामले जिनके बारे में आपको पता होना चाहिए।
तो, उस पर अधिक के लिए आधिकारिक डॉक्स देखना सुनिश्चित करें।
RBS अभी भी बहुत ताज़ा है लेकिन पहले से ही उन Rubyists पर भारी प्रभाव डाल चुका है जो अन्य टूल्स के साथ अपने कोडबेस को टाइप चेक करने के आदी हैं।
आपके बारे में क्या? क्या आपने इसे आज़माया है? RBS पर आपके क्या विचार हैं?
P.S. यदि आप Ruby Magic पोस्ट को प्रेस से निकलते ही पढ़ना चाहते हैं, तो हमारे Ruby Magic न्यूज़लेटर की सदस्यता लें और कभी भी एक भी पोस्ट मिस न करें!