2010-11-04 16 views
13

मैं मेटा प्रोग्रामिंग पर एक लेख पढ़ रहा था और यह दिखाता है कि आप किसी अन्य विधि के भीतर एक विधि को परिभाषित कर सकते हैं। यह ऐसा कुछ है जिसे मैं थोड़ी देर के लिए जानता था, लेकिन उसने मुझे खुद से सवाल पूछा: क्या इसका कोई व्यावहारिक अनुप्रयोग है? क्या विधि के भीतर किसी विधि को परिभाषित करने का कोई वास्तविक जीवन उपयोग है?रूबी: किसी अन्य विधि के अंदर किसी विधि को परिभाषित करने से कोई वास्तविक उपयोग होता है?

पूर्व:

def outer_method 
    def inner_method 
    # ... 
    end 
    # ... 
end 
+0

रूबी डेवलपर्स के छोटे समुदाय में मुझे लगता है। – zengr

+5

क्या आप जानते हैं कि एक बार जब आप 'outer_method' कहते हैं, तो कोई भी' inner_method' कह सकता है? –

उत्तर

11

इस तरह का मेरा पसंदीदा मेटाप्रोग्रामिंग उदाहरण गतिशील रूप से एक विधि का निर्माण कर रहा है जिसे आप लूप में उपयोग करने जा रहे हैं। उदाहरण के लिए, मेरे पास रुबी में एक प्रश्न-इंजन लिखा गया है, और इसके संचालन में से एक फ़िल्टरिंग है। फिल्टर के विभिन्न रूपों का एक गुच्छा है (सबस्ट्रिंग, बराबर, < =,> =, चौराहे, आदि)। अनुभवहीन दृष्टिकोण इस तरह है:

def process_filter(working_set,filter_type,filter_value) 
    working_set.select do |item| 
    case filter_spec 
     when "substring" 
     item.include?(filter_value) 
     when "equals" 
     item == filter_value 
     when "<=" 
     item <= filter_value 
     ... 
    end 
    end 
end 

लेकिन अपने वर्किंग सेट बड़ी प्राप्त कर सकते हैं, तो आप इस बड़े मामले बयान 1000s या समय की 1000000s प्रत्येक ऑपरेशन भले ही यह हर पर एक ही शाखा लेने के लिए जा रहा है के लिए कर रहे हैं यात्रा। मेरे मामले में तर्क सिर्फ एक केस स्टेटमेंट से ज्यादा जुड़ा हुआ है, इसलिए ओवरहेड भी बदतर है। इसके बजाय, आप इसे इस तरह कर सकते हैं:

def process_filter(working_set,filter_type,filter_value) 
    case filter_spec 
    when "substring" 
     def do_filter(item,filter_value) 
     item.include?(filter_value) 
     end 
    when "equals" 
     def do_filter(item,filter_value) 
     item == filter_value 
     end 
    when "<=" 
     def do_filter(item,filter_value) 
     item <= filter_value 
     end 
    ... 
    end 
    working_set.select {|item| do_filter(item,filter_value)} 
end 

अब एक बार शाखाओं में एक बार किया जाता है, सामने, और जिसके परिणामस्वरूप एकल उद्देश्य समारोह भीतरी पाश में प्रयोग किया जाता है।

असल में, मेरा असली उदाहरण इस के तीन स्तर करता है, क्योंकि कामकाजी सेट और फिल्टर मूल्य दोनों की व्याख्या में विविधताएं हैं, न केवल वास्तविक परीक्षण के रूप में। तो मैं एक आइटम-प्री फ़ंक्शन और फ़िल्टर-मान-प्री फ़ंक्शन का निर्माण करता हूं, और फिर उन का उपयोग करने वाले do_filter फ़ंक्शन का निर्माण करता हूं।

(और मैं वास्तव में lambdas, नहीं defs का उपयोग करें।)

+0

महान उदाहरण, बहुत बहुत धन्यवाद! – agentbanks217

+6

अंत में उल्लेख किया गया है, यह एक विधि परिभाषित करने के लिए * lambdas * के लिए एक अच्छा मामला है। इस उद्देश्य के लिए कक्षा पर एक नई विधि को परिभाषित करना गंभीर ओवरकिल है। – Chuck

+3

क्या मैं पूछ सकता हूं कि यह 'गंभीर ओवरकिल' क्यों है? मेरी समझ के लिए यह विधि को एक चर सही बनाने का एक अंतर है? – lulalala

5

हाँ, वहाँ रहे हैं। वास्तव में, मैं शर्त लगाता हूं कि आप कम से कम एक विधि का उपयोग करें जो हर दिन एक और विधि को परिभाषित करता है: attr_accessor। यदि आप रेल का उपयोग करते हैं, तो निरंतर उपयोग में एक टन अधिक है, जैसे कि belongs_to और has_many। यह आम तौर पर एओपी-स्टाइल संरचनाओं के लिए उपयोगी भी है।

+0

ये विधियां अन्य विधियों को परिभाषित करती हैं, लेकिन उनके कार्यान्वयन प्रश्न में उदाहरण की तरह नहीं दिखते हैं। ये विधियां * कक्षा * विधियां हैं जो * example * विधियों को परिभाषित करने के लिए 'define_method' का उपयोग करती हैं। यदि वे 'def' का उपयोग करते हैं, तो वे अधिक * कक्षा * विधियों को परिभाषित करेंगे, जो उपयोगी नहीं होंगे। वे नई विधि का नाम तर्क के रूप में भी नहीं ले सके, क्योंकि 'def' एक ऐसी विधि नहीं है जो तर्क लेता है, यह एक वाक्य रचनात्मक निर्माण है जिसे स्रोत कोड में शाब्दिक नाम की आवश्यकता होती है। – Peeja

+0

(असल में, मामूली सुधार: कुछ मामलों में, मैक्रोज़ इन्हें 'define_method' का उपयोग नहीं करते हैं, बल्कि इसके बजाय एक स्ट्रिंग का निर्माण करते हैं जिसमें' def' निर्माण और 'class_eval' स्ट्रिंग होती है। लेकिन यह अभी भी एक जैसा घोंसला नहीं है 'def' के भीतर 'def'।) – Peeja

+0

(मैंने यह नहीं देखा कि सवाल तकनीकी रूप से' def 'में' def 'के बारे में नहीं है, लेकिन अन्य तरीकों से विधियों को परिभाषित करने के बारे में है। आपने जो कहा है वह बिल्कुल है ठीक है, आपको इसे अपने उदाहरण में दिखाए गए ओपी के तरीके से नहीं करना चाहिए।) – Peeja

0

मैं एक पुनरावर्ती स्थिति के बारे में सोच रहा था, लेकिन मुझे नहीं लगता कि यह काफी मतलब होता है।

5

मुझे लगता है कि स्पष्टता के बारे में आंतरिक तरीकों का उपयोग करने का एक और लाभ है। इसके बारे में सोचें: विधियों की सूची वाला एक वर्ग विधियों की एक फ्लैट, गैर-संरचित सूची है। यदि आप चिंताओं को अलग करने और सामान को समान स्तर पर रखने के बारे में परवाह करते हैं और कोड का टुकड़ा केवल एक ही स्थान पर उपयोग किया जाता है, तो आंतरिक तरीकों से मदद मिलती है जबकि दृढ़ता से संकेत मिलता है कि उनका उपयोग केवल संलग्न विधि में किया जाता है।

मान लें कि एक वर्ग में इस विधि है:

class Scoring 
    # other code 
    def score(dice) 
    same, rest = split_dice(dice) 

    set_score = if same.empty? 
     0 
    else 
     die = same.keys.first 
     case die 
     when 1 
     1000 
     else 
     100 * die 
     end 
    end 
    set_score + rest.map { |die, count| count * single_die_score(die) }.sum 
    end 

    # other code 
end 

अब, यह एक तरह से सरल डेटा संरचना परिवर्तन और अधिक उच्च स्तर कोड है, एक सेट बनाने पासों का स्कोर जोड़ने और वे जो से संबंधित नहीं है सेट। लेकिन यह स्पष्ट नहीं है कि क्या हो रहा है। चलो इसे और अधिक वर्णनात्मक बनाते हैं। एक साधारण रिफैक्टरिंग इस प्रकार है: get_set_score() और get_rest_score (के

class Scoring 
    # other methods... 
    def score(dice) 
    same, rest = split_dice(dice) 

    set_score = same.empty? ? 0 : get_set_score(same) 
    set_score + get_rest_score(rest) 
    end 

    def get_set_score(dice) 
    die = dice.keys.first 
    case die 
    when 1 
     1000 
    else 
     100 * die 
    end 
    end 

    def get_rest_score(dice) 
    dice.map { |die, count| count * single_die_score(die) }.sum 
    end 

    # other code... 
end 

आइडिया) एक वर्णनात्मक (हालांकि नहीं बहुत अच्छा में इस उदाहरण ही गाढ़ा) उन टुकड़े करना क्या का उपयोग करके दस्तावेज़ के लिए है।लेकिन अगर आपके पास इस तरह की कई विधियां हैं, तो स्कोर में कोड() का पालन करना इतना आसान नहीं है, और यदि आप किसी भी तरीके से दोबारा प्रतिक्रिया देते हैं तो आपको यह जांचना होगा कि अन्य विधियों का उपयोग किस प्रकार किया जाता है (भले ही वे निजी हैं - अन्य उसी वर्ग के तरीके उनका उपयोग कर सकते हैं)। में यहाँ

class Scoring 
    # other code 
    def score(dice) 
    def get_set_score(dice) 
     die = dice.keys.first 
     case die 
     when 1 
     1000 
     else 
     100 * die 
     end 
    end 

    def get_rest_score(dice) 
     dice.map { |die, count| count * single_die_score(die) }.sum 
    end 

    same, rest = split_dice(dice) 

    set_score = same.empty? ? 0 : get_set_score(same) 
    set_score + get_rest_score(rest) 
    end 

    # other code 
end 

, यह और भी स्पष्ट है कि get_rest_score() और get_set_score() पद्धतियों में लपेटा जाता है स्कोर (के तर्क रखने के लिए होना चाहिए) ही:

इसके बजाय, मैं इस को पसंद करते हैं शुरू कर एक ही अमूर्त स्तर, हैश आदि

नोट के साथ कोई दखल है कि तकनीकी रूप से आप कॉल स्कोरिंग # get_set_score और स्कोरिंग # get_rest_score, लेकिन इस मामले में यह बुरा शैली IMO होगा सकता है, क्योंकि अर्थ की दृष्टि से वे के लिए सिर्फ निजी तरीके हैं एकल विधि स्कोर()

तो, इस संरचना के साथ आप स्कोरिंग # स्कोर के बाहर परिभाषित किसी भी अन्य विधि को देखे बिना स्कोर() के पूरे कार्यान्वयन को हमेशा पढ़ने में सक्षम हैं। हालांकि मुझे अक्सर इस तरह के रूबी कोड नहीं दिखते हैं, मुझे लगता है कि मैं इस संरचित शैली में आंतरिक तरीकों से अधिक रूपांतरित करने जा रहा हूं।

नोट: एक और विकल्प जो साफ दिखता नहीं है लेकिन नाम संघर्षों के मुद्दे से बचाता है, केवल लम्बास का उपयोग करना है, जो रूबी में घूमने से आसपास है। उदाहरण का उपयोग करना, यह

get_rest_score = -> (dice) do 
    dice.map { |die, count| count * single_die_score(die) }.sum 
end 
... 
set_score + get_rest_score.call(rest) 

यह नहीं के रूप में सुंदर है में बदल जाते हैं होगा - कोई कोड को देखकर आश्चर्य हो सकता है क्यों इन सभी lambdas, जबकि भीतरी तरीकों का उपयोग कर सुंदर आत्म दस्तावेज़ीकृत है। मैं अभी भी सिर्फ lambdas की तरफ झुकना चाहता हूं, क्योंकि उनके पास मौजूदा विवादों के संभावित संभावित नामों को लीक करने का मुद्दा नहीं है।

+0

मुझे एक ही अभ्यास पसंद है। कोड और स्पष्टता को सरल बनाने के तरीकों के भीतर विधियों को परिभाषित करना। यह बहुत उपयोगी होता है जब आपकी विधि भारी (जटिलता) हो जाती है और आप कॉल के लिए outer_method को अलग नहीं करना चाहते हैं। – ajahongir

+0

यह एक अच्छा विचार नहीं है। रूबी '# स्कोर' के अंदर' def' को नोटिस नहीं करता है और विधि * एक बार * को परिभाषित करता है, यह हर बार इसे परिभाषित करता है (और केवल तभी) '# स्कोर' चलता है।इसका अर्थ यह है कि '# get_set_score' तब तक अस्तित्व में नहीं है जब तक कि' # स्कोर' नहीं कहा जाता है * और * प्रत्येक बार '# get_set_score' कहलाता है' को फिर से परिभाषित किया जाता है। न केवल अजीब है, यह रूबी के वैश्विक विधि कैश को भी अमान्य करता है, जो आपके प्रोग्राम को नाटकीय रूप से धीमा कर देगा। – Peeja

+0

पूरी तरह से पिज्जा से सहमत हैं, इसलिए जब तक कि भाषा सही आंतरिक तरीकों का समर्थन नहीं करती है, तब तक उनका उपयोग करने की संभावना नहीं है (शायद रूबी को चेतावनी छोड़नी चाहिए यदि यह ऐसा पता चलता है, या यहां तक ​​कि एक त्रुटि भी है?) – EdvardM

3

def का उपयोग नहीं कर रहा है। इसके लिए कोई व्यावहारिक अनुप्रयोग नहीं है, और संकलक शायद एक त्रुटि उठाएगा।

किसी अन्य विधि को निष्पादित करने के दौरान गतिशील रूप से एक विधि को परिभाषित करने के कारण हैं। attr_reader, जो सी में कार्यान्वित किया जाता है पर विचार करें, लेकिन समतुल्य रूप के रूप में रूबी में लागू किया जा सकता:

class Module 
    def attr_reader(name) 
    define_method(name) do 
     instance_variable_get("@#{name}") 
    end 
    end 
end 

यहाँ, हम #define_method का उपयोग विधि को परिभाषित करने के। #define_method एक वास्तविक विधि है; def नहीं है। यह हमें दो महत्वपूर्ण गुण देता है। सबसे पहले, यह एक तर्क लेता है, जो हमें विधि को नाम देने के लिए name चर को पारित करने की अनुमति देता है। दूसरा, यह एक ब्लॉक लेता है, जो हमारे चर name पर बंद हो जाता है जो हमें इसे विधि परिभाषा के अंदर से उपयोग करने की इजाजत देता है।

तो क्या होता है यदि हम इसके बजाय def का उपयोग करते हैं?

class Module 
    def attr_reader(name) 
    def name 
     instance_variable_get("@#{name}") 
    end 
    end 
end 

यह बिल्कुल काम नहीं करता है। सबसे पहले, def कीवर्ड का शाब्दिक नाम है, अभिव्यक्ति नहीं। इसका मतलब है कि हम नामित एक विधि को परिभाषित कर रहे हैं, शाब्दिक रूप से, #name, जो हम बिल्कुल नहीं चाहते थे। दूसरा, विधि का शरीर name नामक स्थानीय चर को संदर्भित करता है, लेकिन रूबी इसे #attr_reader पर तर्क के समान वैरिएबल के रूप में नहीं पहचान पाएगा। def निर्माण ब्लॉक का उपयोग नहीं करता है, इसलिए यह अब name परिवर्तनीय को बंद नहीं करता है।

def निर्माण आपको परिभाषित करने की विधि की परिभाषा को पैरामीटर करने के लिए किसी भी जानकारी को "पास" करने की अनुमति नहीं देता है। यह एक गतिशील संदर्भ में बेकार बनाता है। किसी विधि के भीतर से def का उपयोग करके विधि को परिभाषित करने का कोई कारण नहीं है। आप बाहरी def बाहरी def से बाहर एक ही आंतरिक def को हमेशा स्थानांतरित कर सकते हैं और उसी विधि के साथ समाप्त हो सकते हैं।


इसके अतिरिक्त, परिभाषित तरीकों को गतिशील रूप से लागत है। रूबी विधियों के स्मृति स्थानों को कैश करता है, जो प्रदर्शन में सुधार करता है। जब आप किसी वर्ग से कोई विधि जोड़ते या हटाते हैं, तो रूबी को उस कैश को फेंकना पड़ता है। (रूबी 2.1 से पहले, कि कैश वैश्विक था। 2.1 के रूप में, कैश प्रति वर्ग है।)

आप किसी अन्य विधि के अंदर एक विधि को परिभाषित करते हैं, तो हर बार बाहरी विधि कहा जाता है, यह कैश अमान्य हो जाएगा। attr_reader और रेल के belongs_to जैसे शीर्ष-स्तरीय मैक्रोज़ के लिए यह ठीक है, क्योंकि उन सभी को प्रोग्राम शुरू होने पर और फिर (उम्मीद है) कभी भी नहीं कहा जाता है। आपके प्रोग्राम के चल रहे निष्पादन के दौरान परिभाषित तरीकों से आपको थोड़ा धीमा कर दिया जाएगा।

संबंधित मुद्दे

 संबंधित मुद्दे