क्या यह विधि शुद्ध है?


9

मेरे पास निम्नलिखित एक्सटेंशन विधि है:

    public static IEnumerable<T> Apply<T>(
        [NotNull] this IEnumerable<T> source,
        [NotNull] Action<T> action)
        where T : class
    {
        source.CheckArgumentNull("source");
        action.CheckArgumentNull("action");
        return source.ApplyIterator(action);
    }

    private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
        where T : class
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }

इसे वापस करने से पहले यह अनुक्रम के प्रत्येक आइटम पर एक क्रिया लागू करता है।

मैं सोच रहा था कि क्या मुझे इस विधि में Pureविशेषता (पुनर्वसन एनोटेशन से) लागू करना चाहिए , और मैं इसके लिए और इसके खिलाफ तर्क देख सकता हूं।

पेशेवरों:

  • कडाई के साथ यह है शुद्ध; बस इसे एक अनुक्रम पर कॉल करने से अनुक्रम में बदलाव नहीं होता है (यह एक नया अनुक्रम देता है) या किसी भी अवलोकन स्थिति को बदल सकता है
  • परिणाम का उपयोग किए बिना इसे कॉल करना स्पष्ट रूप से एक गलती है, क्योंकि इसका कोई प्रभाव नहीं है जब तक कि अनुक्रम की गणना नहीं की जाती है, इसलिए मैं चाहूंगा कि अगर मैं ऐसा करता हूं तो मुझे चेतावनी दें।

विपक्ष:

  • भले ही Applyविधि ही शुद्ध है, गणना जिसके परिणामस्वरूप अनुक्रम जाएगा नमूदार राज्य परिवर्तन कर (जो विधि की बात है)। उदाहरण के लिए, items.Apply(i => i.Count++)हर बार एन्यूमरेट किए गए आइटम के मूल्यों को बदल देगा। तो शुद्ध विशेषता को लागू करना शायद भ्रामक है ...

तुम क्या सोचते हो? क्या मुझे विशेषता को लागू करना चाहिए या नहीं?


जवाबों:


15

नहीं, यह शुद्ध नहीं है, क्योंकि इसका दुष्प्रभाव है। लगातार यह actionप्रत्येक आइटम पर कॉल कर रहा है । इसके अलावा, यह थ्रेडसेफ़ नहीं है।

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

इसके अलावा, आपको एक चीज पर गलती हो सकती है: फ़ंक्शन शुद्धता पेशेवरों या विपक्षों का सवाल नहीं है। एक भी संदेह, कि इसका साइड इफेक्ट हो सकता है, इसे शुद्ध नहीं बनाने के लिए पर्याप्त है।

एरिक लिपर्ट एक अच्छी बात उठाते हैं। मैं अपने प्रतिवाद के भाग के रूप में http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx का उपयोग करने जा रहा हूं । खासतौर पर लाइन

शुद्ध पद्धति में प्रवेश के बाद बनाई गई वस्तुओं को संशोधित करने के लिए एक शुद्ध विधि की अनुमति है।

हम कहते हैं कि हम इस तरह विधि बनाएँ:

int Count<T>(IEnumerable<T> e)
{
    var enumerator = e.GetEnumerator();
    int count = 0;
    while (enumerator.MoveNext()) count ++;
    return count;
}

सबसे पहले, यह मानता है कि GetEnumeratorशुद्ध भी है (मैं वास्तव में उस पर कोई स्रोत नहीं पा सकता हूं)। यदि यह है, तो उपरोक्त नियम के अनुसार, हम इस पद्धति को [शुद्ध] के साथ एनोटेट कर सकते हैं, क्योंकि यह केवल उस उदाहरण को संशोधित करता है जो शरीर के भीतर ही बनाया गया था। उसके बाद हम इसे और यह रचना कर सकते हैं ApplyIterator, जिसके परिणामस्वरूप शुद्ध कार्य करना चाहिए, है ना?

Count(ApplyIterator(source, action));

नहीं, इस रचना शुद्ध नहीं है, तब भी जब दोनों Countऔर ApplyIteratorशुद्ध कर रहे हैं। लेकिन मैं गलत तर्क पर इस तर्क का निर्माण कर सकता हूं। मुझे लगता है कि यह विचार कि विधि के भीतर निर्मित उदाहरणों को शुद्धता नियम से छूट दी गई है या तो गलत है या कम से कम पर्याप्त विशिष्ट नहीं है।


1
+1 फ़ंक्शन शुद्धता पेशेवरों या विपक्षों का सवाल नहीं है। फ़ंक्शन शुद्धता उपयोग और सुरक्षा पर एक संकेत है। अजीब तरह से पर्याप्त ओपी में डाल दिया where T : class, हालांकि अगर ओपी बस डाल where T : strutयह शुद्ध होना चाहिए।
ArTs

4
मैं इस जवाब से असहमत हूं। कॉलिंग sequence.Apply(action)का कोई दुष्प्रभाव नहीं है; अगर ऐसा होता है, तो इसके साइड इफेक्ट्स बताएं। अब, कॉलिंग sequence.Apply(action).GetEnumerator().MoveNext()का साइड इफेक्ट है, लेकिन हम पहले से ही जानते थे कि; यह प्रगणक उत्परिवर्तित करता है! क्यों sequence.Apply(action)अपवित्र माना जाना चाहिए क्योंकि कॉलिंग MoveNextअशुद्ध है, लेकिन sequence.Where(predicate)शुद्ध माना जाता है? sequence.Where(predicate).GetEnumerator().MoveNext()अशुद्ध के रूप में हर बिट है
एरिक लिपर्ट

@EricLippert आप एक अच्छी बात उठाते हैं। लेकिन, क्या यह सिर्फ गेटइन्यूमर को बुलाने के लिए पर्याप्त नहीं होगा? क्या हम उस शुद्ध पर विचार कर सकते हैं?
व्यंग्यात्मक

@ यूफोरिक: GetEnumeratorअपनी प्रारंभिक अवस्था में एक एन्यूमरेटर आवंटित करने से हटकर, कौन से अवलोकन योग्य प्रभाव का उत्पादन होता है ?
एरिक लिपर्ट

1
@EricLippert तो ऐसा क्यों है कि Enumerable.Count को .NET के कोड कॉन्ट्रैक्ट्स द्वारा शुद्ध माना जाता है? मेरे पास लिंक नहीं है, लेकिन जब मैं दृश्य स्टूडियो में इसके साथ खेलता हूं, तो कस्टम गैर-शुद्ध गणना का उपयोग करने पर मुझे चेतावनी मिलती है, लेकिन अनुबंध एन्युमरेबल के साथ ठीक काम करता है।
व्यंग्यात्मक

18

मैं यूफोरिक और रॉबर्ट हार्वे के जवाबों से असहमत हूं । बिलकुल एक शुद्ध कार्य है; समस्या यह है कि

इसे वापस करने से पहले यह अनुक्रम के प्रत्येक आइटम पर एक क्रिया लागू करता है।

बहुत स्पष्ट नहीं है कि पहला "यह" क्या मतलब है। यदि "यह" उन कार्यों में से एक का अर्थ है, तो यह सही नहीं है; उन कार्यों में से कोई भी ऐसा नहीं करता है; MoveNextअनुक्रम के प्रगणक की है कि करता है, और यह "रिटर्न" के माध्यम से आइटम Currentसंपत्ति है, यह वापस लौट कर नहीं।

उन दृश्यों को आलस्य से भरा हुआ है , उत्सुकता से नहीं तो यह निश्चित रूप से ऐसा नहीं है कि अनुक्रम द्वारा वापस आने से पहले कार्रवाई को लागू किया जाता है Apply। अनुक्रम वापस करने के बाद कार्रवाई लागू की जाती है, अगर MoveNextएक एन्यूमरेटर पर कहा जाता है।

जैसा कि आप ध्यान दें, ये कार्य एक कार्रवाई और एक अनुक्रम लेते हैं और एक अनुक्रम वापस करते हैं; आउटपुट इनपुट पर निर्भर करता है, और कोई दुष्प्रभाव उत्पन्न नहीं होता है, इसलिए ये शुद्ध कार्य हैं ..

अब, यदि आप परिणामी अनुक्रम का एन्यूमरेटर बनाते हैं और फिर उस पुनरावृत्ति पर MoveNext को कॉल करते हैं तो MoveNext विधि शुद्ध नहीं होती है, क्योंकि यह क्रिया को कॉल करती है और एक साइड इफेक्ट का उत्पादन करती है। लेकिन हम पहले से ही जानते थे कि MoveNext शुद्ध नहीं था क्योंकि यह एन्यूमरेटर को बदल देता है!

अब, आपके प्रश्न के लिए, क्या आपको विशेषता लागू करनी चाहिए: मैं इस विशेषता को लागू नहीं करूंगा क्योंकि मैं इस विधि को पहली जगह पर नहीं लिखूंगा । अगर मैं एक सीक्वेंस पर एक्शन लागू करना चाहता हूं तो लिखता हूं

foreach(var item in sequence) action(item);

जो अच्छी तरह से स्पष्ट है।


2
मुझे लगता है कि यह विधि ForEachविस्तार विधि के रूप में एक ही बैग में आती है , जो जानबूझकर Linq का हिस्सा नहीं है क्योंकि इसका लक्ष्य साइड इफेक्ट्स का उत्पादन करना है ...
थॉमस लेवेस्क

1
@ThomasLevesque: मेरी सलाह है कि कभी ऐसा न करें । एक क्वेरी को एक प्रश्न का उत्तर देना चाहिए , एक अनुक्रम को म्यूट नहीं करना चाहिए ; इसलिए उन्हें प्रश्न कहा जाता है । अनुक्रम को म्यूट कर देना क्योंकि यह बहुत ही खतरनाक है । उदाहरण के लिए विचार करें कि क्या होता है यदि ऐसी क्वेरी को Any()समय के साथ कई कॉल के अधीन किया जाता है; कार्रवाई बार-बार की जाएगी, लेकिन केवल पहले आइटम पर! एक क्रम मूल्यों का एक क्रम होना चाहिए ; यदि आप के अनुक्रम चाहते कार्रवाई तो एक बनाने के IEnumerable<Action>
एरिक लिपर्ट

2
यह उत्तर पानी को अधिक से अधिक प्रकाशित करता है, क्योंकि यह प्रकाशित करता है। जबकि आप जो कुछ भी कहते हैं वह निर्विवाद रूप से सत्य है, अपरिवर्तनीयता और शुद्धता के सिद्धांत उच्च-स्तरीय प्रोग्रामिंग भाषा सिद्धांत हैं, न कि निम्न-स्तरीय कार्यान्वयन विवरण। एक कार्यात्मक स्तर पर काम करने वाले प्रोग्रामर इस बात में रुचि रखते हैं कि उनका कोड कार्यात्मक स्तर पर कैसे व्यवहार करता है, न कि इसके आंतरिक कामकाज शुद्ध हैं या नहीं यदि आप पर्याप्त रूप से कम जाते हैं तो वे हुड के नीचे लगभग शुद्ध नहीं हैं। हम सभी आम तौर पर वॉन न्यूमैन वास्तुकला पर इन चीजों को चलाते हैं, जो निश्चित रूप से शुद्ध नहीं है।
रॉबर्ट हार्वे

2
@ThomasEding: विधि कॉल नहीं करती है action, इसलिए की शुद्धता actionअप्रासंगिक है। मुझे पता है कि यह कॉल की तरह लग रहा हैaction , लेकिन यह विधि दो तरीकों के लिए एक वाक्यविन्यास चीनी है, एक जो एक एन्यूमरेटर लौटाता है, और एक जो MoveNextएन्यूमरेटर है। पूर्व स्पष्ट रूप से शुद्ध है, और उत्तरार्द्ध स्पष्ट रूप से नहीं है। इसे इस तरह देखें: क्या आप कहेंगे कि IEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }शुद्ध है? क्योंकि यही वह फंक्शन है जो वास्तव में यही है।
एरिक लिपर्ट

1
@ThomasEding: आप कुछ याद कर रहे हैं; ऐसा नहीं है कि पुनरावृत्तियों कैसे काम करते हैं। ApplyIteratorविधि रिटर्न तुरंत । लौटे हुए ऑब्जेक्ट के एन्यूमरेटर पर ApplyIteratorपहली कॉल होने तक शरीर में कोई कोड नहीं चलाया जाता है MoveNext। अब आप कि पता है कि आप इस पहेली का जवाब यह मान सकते हैं: blogs.msdn.com/b/ericlippert/archive/2007/09/05/... जवाब यहाँ है: blogs.msdn.com/b/ericlippert/archive /
2007/09/06

3

यह एक शुद्ध कार्य नहीं है, इसलिए शुद्ध विशेषता को लागू करना भ्रामक है।

शुद्ध कार्य मूल संग्रह को संशोधित नहीं करते हैं, और इससे कोई फर्क नहीं पड़ता है कि आप एक ऐसी कार्रवाई को पारित कर रहे हैं जिसका कोई प्रभाव नहीं है या नहीं; यह अभी भी एक अशुद्ध कार्य है क्योंकि इसका इरादा साइड इफेक्ट का कारण है।

यदि आप फ़ंक्शन को शुद्ध बनाना चाहते हैं, तो संग्रह को एक नए संग्रह में कॉपी करें, जो परिवर्तन नए संग्रह में किए जाते हैं उन्हें लागू करें, और मूल संग्रह को अपरिवर्तित छोड़ते हुए नए संग्रह को वापस करें।


खैर, यह मूल संग्रह को संशोधित नहीं करता है, क्योंकि यह एक ही आइटम के साथ एक नया अनुक्रम देता है; यही कारण है कि मैं इसे शुद्ध बनाने पर विचार कर रहा था। जब आप परिणाम की गणना करते हैं तो यह आइटम की स्थिति को बदल सकता है।
थॉमस लेवेस्क

यदि itemएक संदर्भ प्रकार है, तो यह मूल संग्रह को संशोधित कर रहा है, भले ही आप itemएक पुनरावृत्ति में लौट रहे हों । देखें stackoverflow.com/questions/1538301
रॉबर्ट हार्वे

1
यहां तक ​​कि अगर वह संग्रह को गहराई से कॉपी करता है, तो यह अभी भी शुद्ध नहीं होगा, क्योंकि actionइसमें आइटम को संशोधित करने के अलावा अन्य दुष्प्रभाव हो सकते हैं।
इदं आर्य

@IdanArye: सच है, एक्शन को भी शुद्ध होना होगा।
रॉबर्ट हार्वे

1
@IdanArye: ()=>{}एक्शन के लिए परिवर्तनीय है, और यह एक शुद्ध कार्य है। यह आउटपुट केवल इसके इनपुट पर निर्भर करता है और इसका कोई भी दुष्प्रभाव नहीं है।
एरिक लिपर्ट

0

मेरी राय में, यह तथ्य कि इसे एक्शन मिलता है (और प्योरएशन जैसा कुछ नहीं) इसे शुद्ध नहीं बनाता है।

और मैं एरिक लिपर्ट से भी असहमत हूं। उन्होंने यह लिखा "" () => {} एक्शन के लिए परिवर्तनीय है, और यह एक शुद्ध कार्य है। यह आउटपुट केवल इसके इनपुट पर निर्भर करता है और इसका कोई अवलोकन करने योग्य दुष्प्रभाव नहीं है "।

ठीक है, कल्पना कीजिए कि एक प्रतिनिधि का उपयोग करने के बजाय ApplyIterator एक्शन नामक एक विधि का आह्वान कर रहा था।

यदि Action शुद्ध है, तो ApplyIterator भी शुद्ध है। यदि कार्रवाई शुद्ध नहीं है, तो ApplyIterator शुद्ध नहीं हो सकता है।

प्रतिनिधि प्रकार (वास्तविक दिए गए मूल्य नहीं) को देखते हुए, हमारे पास यह गारंटी नहीं है कि यह शुद्ध होगा, इसलिए विधि केवल शुद्ध विधि के रूप में व्यवहार करेगी जब प्रतिनिधि शुद्ध होगा। तो, इसे वास्तव में शुद्ध बनाने के लिए, इसे एक शुद्ध प्रतिनिधि मिलना चाहिए (और यह मौजूद है, हम एक प्रतिनिधि को [शुद्ध] घोषित कर सकते हैं, इसलिए हमारे पास एक शुद्धिकरण हो सकता है)।

इसे अलग तरीके से समझाते हुए, एक शुद्ध विधि को हमेशा एक ही इनपुट दिया जाना चाहिए और एक ही परिणाम देना चाहिए और यह देखने योग्य परिवर्तन उत्पन्न नहीं करना चाहिए। ApplyIterator को एक ही स्रोत और दो बार प्रतिनिधि दिया जा सकता है, लेकिन यदि प्रतिनिधि संदर्भ-प्रकार को बदल रहा है, तो अगला निष्पादन अलग परिणाम देगा। उदाहरण: प्रतिनिधि कुछ ऐसा करता है जैसे कि आइटम। कंटेंट + = "परिवर्तित";

इसलिए, "स्ट्रिंग कंटेनर" (प्रकार स्ट्रिंग की सामग्री की संपत्ति के साथ एक वस्तु) की एक सूची पर अप्लाईटर का उपयोग करते हुए, हमारे पास ये मूल मूल्य हो सकते हैं:

Test

Test2

पहले निष्पादन के बाद, सूची में यह होगा:

Test Changed

Test2 Changed

और यह तीसरी बार:

Test Changed Changed

Test2 Changed Changed

इसलिए, हम सूची की सामग्री को बदल रहे हैं क्योंकि प्रतिनिधि शुद्ध नहीं है और 3 बार कॉल किए जाने पर कॉल को निष्पादित करने से बचने के लिए कोई अनुकूलन नहीं किया जा सकता है, क्योंकि प्रत्येक निष्पादन एक अलग परिणाम उत्पन्न करेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.