मेमोइजेशन एक तकनीक है जिसका उपयोग आप अपने एक्सेसर मेथड को तेज करने के लिए कर सकते हैं। यह उन मेथड के परिणामों को कैश करता है जो समय लेने वाला काम करते हैं, ऐसा काम जिसे केवल एक बार करने की आवश्यकता होती है। रेल्स में, मेमोइजेशन का इतनी बार उपयोग किया जाता है कि इसमें एक मॉड्यूल भी शामिल था जो आपके लिए मेथड को मेमोइज़ करता था।
बाद में, इसे विवादास्पद रूप से केवल उस वास्तव में सामान्य मेमोइजेशन पैटर्न के पक्ष में हटा दिया गया जिसके बारे में मैं पहले बात करूंगा। लेकिन जैसा कि आप देखेंगे, कुछ जगहें हैं जहां यह बुनियादी पैटर्न सही ढंग से काम नहीं करता। इसलिए हम अधिक उन्नत मेमोइजेशन पैटर्न भी देखेंगे, और इस प्रक्रिया में रूबी के बारे में कुछ अच्छी बातें सीखेंगे!
सुपर बेसिक मेमोइजेशन
आप रूबी में हर समय इस मेमोइजेशन पैटर्न को देखेंगे:
class User < ActiveRecord::Base def twitter_followers # assuming twitter_user.followers makes a network call @twitter_followers ||= twitter_user.followers end end ||= कम या ज्यादा @twitter_followers = @twitter_followers || twitter_user.followers में अनुवाद करता है। इसका मतलब है कि आप पहली बार twitter_followers को कॉल करने पर ही नेटवर्क कॉल करेंगे, और भविष्य के कॉल सिर्फ इंस्टेंस वेरिएबल @twitter_followers का मान वापस कर देंगे।
मल्टी-लाइन मेमोइजेशन
कभी-कभी, धीमा कोड भयानक चीजें किए बिना एक लाइन पर फिट नहीं होगा। बुनियादी पैटर्न को मल्टीपल लाइनों के कोड के साथ काम करने के लिए बढ़ाने के कुछ तरीके हैं, लेकिन यह मेरा पसंदीदा है:
class User < ActiveRecord::Base def main_address @main_address ||= begin maybe_main_address = home_address if prefers_home_address? maybe_main_address = work_address unless maybe_main_address maybe_main_address = addresses.first unless maybe_main_address end end end begin...end रूबी में कोड का एक ब्लॉक बनाता है जिसे एक चीज के रूप में माना जा सकता है, कुछ-कुछ C-स्टाइल भाषाओं में {...} की तरह। इसलिए ||= यहां भी उतना ही अच्छा काम करता है जितना पहले करता था।
nil के बारे में क्या?
लेकिन इन मेमोइजेशन पैटर्न में एक बुरा, छिपा हुआ प्रॉब्लम है। पहले उदाहरण में, क्या होगा अगर यूजर का ट्विटर अकाउंट न हो, और ट्विटर फॉलोअर्स API nil वापस करे? दूसरे में, क्या होगा अगर यूजर के पास कोई एड्रेस न हो, और ब्लॉक nil वापस करे?
हर बार जब हम मेथड को कॉल करेंगे, इंस्टेंस वेरिएबल nil होगा, और हम फिर से महंगी फेचिंग करेंगे।
तो, ||= शायद जाने का सही तरीका नहीं है। इसके बजाय, हमें nil और undefined के बीच अंतर करना होगा:
class User < ActiveRecord::Base def twitter_followers return @twitter_followers if defined? @twitter_followers @twitter_followers = twitter_user.followers end end class User < ActiveRecord::Base def main_address return @main_address if defined? @main_address @main_address = begin main_address = home_address if prefers_home_address? main_address ||= work_address main_address ||= addresses.first # some semi-sensible default end end end दुर्भाग्य से, यह थोड़ा बदसूरत है, लेकिन यह nil, false, और बाकी सब चीजों के साथ काम करता है। (nil केस को संभालने के लिए, आप नल ऑब्जेक्ट्स और खाली सरणियों का भी उपयोग कर सकते हैं इस समस्या से बचने के लिए। nil से बचने का एक और कारण!)
और पैरामीटर्स के बारे में क्या?
हमारे पास कुछ मेमोइजेशन पैटर्न हैं जो सिंपल एक्सेसर्स के लिए अच्छा काम करते हैं। लेकिन क्या होगा अगर आप एक ऐसे मेथड को मेमोइज़ करना चाहते हैं जो पैरामीटर लेता है, जैसे यह वाला?
class City < ActiveRecord::Base def self.top_cities(order_by) where(top_city: true).order(order_by).to_a end end यह पता चला है कि रूबी के Hash में एक इनिशियलाइज़र है जो इस स्थिति के लिए पूरी तरह काम करता है। आप एक ब्लॉक के साथ Hash.new कॉल कर सकते हैं:
Hash.new {|h, key| h[key] = some_calculated_value } फिर, हर बार जब आप हैश में एक ऐसी कुंजी को एक्सेस करने की कोशिश करते हैं जो सेट नहीं की गई है, तो यह ब्लॉक को एक्जीक्यूट करेगा। और यह ब्लॉक में खुद हैश के साथ-साथ आपके द्वारा एक्सेस की गई कुंजी को पास करेगा।
तो, अगर आप इस मेथड को मेमोइज़ करना चाहते हैं, तो आप ऐसा कुछ कर सकते हैं:
class City < ActiveRecord::Base def self.top_cities(order_by) @top_cities ||= Hash.new do |h, key| h[key] = where(top_city: true).order(key).to_a end @top_cities[order_by] end end और चाहे आप order_by में कुछ भी पास करें, सही परिणाम मेमोइज़ हो जाएगा। चूंकि ब्लॉक को केवल तब कॉल किया जाता है जब कुंजी मौजूद नहीं होती, आपको ब्लॉक के परिणाम के nil या false होने की चिंता करने की आवश्यकता नहीं है।
आश्चर्यजनक रूप से, Hash उन कुंजियों के साथ ठीक काम करता है जो वास्तव में सरणियाँ हैं:
h = {} h[["a", "b"]] = "c" h[["a", "b"]] # => "c" तो आप इस पैटर्न का उपयोग किसी भी संख्या के पैरामीटर वाले मेथड में कर सकते हैं!
यह सब परेशानी क्यों उठाएं?
बेशक, अगर आप इन मेमोइजेशन पैटर्न को बहुत सारे मेथड में जोड़ना शुरू करते हैं, तो आपका कोड बहुत जल्दी अपठनीय हो जाएगा। आपके मेथड केवल समारोह होंगे और कोई सार नहीं।
इसलिए यदि आप एक ऐसे ऐप पर काम कर रहे हैं जिसे बहुत अधिक मेमोइजेशन की आवश्यकता है, तो आप एक ऐसे जेम का उपयोग करना चाह सकते हैं जो एक अच्छे, फ्रेंडली API के साथ आपके लिए मेमोइजेशन को संभालता है। Memoist एक अच्छा विकल्प लगता है, और यह रेल्स में पहले जो था उसके काफी समान है। (या, अपने नए मेमोइजेशन ज्ञान के साथ, आप खुद एक बनाने की कोशिश भी कर सकते हैं)।
लेकिन इस तरह के पैटर्न की जांच करना हमेशा दिलचस्प होता है, देखें कि वे कैसे बनाए गए हैं, वे कहां काम करते हैं, और जहां तेज धारें हैं। और आप कुछ कम-ज्ञात रूबी सुविधाओं के बारे में अच्छी चीजें सीख सकते हैं जब आप उनकी खोज करते हैं।