आलसी मूल्यांकन की अवधारणा क्यों उपयोगी है?


30

ऐसा लगता है कि अभिव्यक्तियों का आलसी मूल्यांकन एक प्रोग्रामर को उस आदेश पर नियंत्रण खोने का कारण बन सकता है जिसमें उनके कोड को निष्पादित किया गया है। मुझे यह समझने में परेशानी हो रही है कि यह प्रोग्रामर द्वारा स्वीकार्य या वांछित क्यों होगा।

इस प्रतिमान का उपयोग पूर्वानुमेय सॉफ़्टवेयर के निर्माण के लिए कैसे किया जा सकता है जो पहले से ही काम करता है, जब हमारे पास कोई गारंटी नहीं है कि एक अभिव्यक्ति का मूल्यांकन कब और कहाँ किया जाएगा?


10
ज्यादातर मामलों में यह सिर्फ मायने नहीं रखता। अन्य सभी के लिए आप सिर्फ सख्ती लागू कर सकते हैं।
कैट प्लस प्लस

22
हैस्केल जैसी विशुद्ध रूप से कार्यात्मक भाषाओं का मुद्दा यह है कि जब कोड चलाया जाता है तो आपको परेशान नहीं होना पड़ता है, क्योंकि यह साइड-इफ़ेक्ट फ्री है।
बिटमस्क

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

6
शीर्षक में सवाल ("आलसी मूल्यांकन का उपयोग क्यों करें?") शरीर में सवाल से बहुत अलग है ("आप आलसी मूल्यांकन का उपयोग कैसे करते हैं?")। पूर्व के लिए, इस संबंधित प्रश्न के लिए मेरा उत्तर देखें ।
डैनियल वैगनर

1
एक उदाहरण जब आलस्य उपयोगी होता है: हस्केल head . sortमें O(n)आलस्य (नहीं O(n log n)) के कारण जटिलता होती है । आलसी मूल्यांकन और समय जटिलता देखें ।
पेट्र पुडलक

जवाबों:


62

गणना के कई हिस्सों में से कई उत्तर अनंत सूचियों और प्रदर्शन लाभ जैसी चीजों में जा रहे हैं, लेकिन यह आलस्य के लिए बड़ी प्रेरणा गायब है: प्रतिरूपकता

जॉन ह्यूजेस द्वारा बहु-उद्धृत पेपर "व्हाई फंक्शनल प्रोग्रामिंग मैटर्स" (पीडीएफ लिंक) में क्लासिक तर्क दिया गया है । उस पत्र में मुख्य उदाहरण (धारा 5) अल्फा-बीटा खोज एल्गोरिथ्म का उपयोग करके टिक-टैक-टो खेल रहा है। मुख्य बिंदु है (पृष्ठ 9):

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

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

एक उत्सुक भाषा में, आप इसे इस तरह नहीं लिख सकते क्योंकि आप शायद बहुत अधिक समय और स्मृति को पेड़ बनाने में खर्च करेंगे। तो आप या तो समाप्त करें:

  1. पीढ़ी और खपत को एक ही फ़ंक्शन में संयोजित करना;
  2. एक निर्माता लिखना जो केवल कुछ उपभोक्ताओं के लिए ही बेहतर काम करता है;
  3. आलस्य के अपने स्वयं के संस्करण को लागू करना।

कृपया अधिक जानकारी या एक उदाहरण। यह पेचीदा लगता है।
एलेक्स नी

1
@AlexNye: जॉन ह्यूजेस पेपर में अधिक जानकारी है। एक शैक्षणिक पेपर होने के बावजूद --- और इसलिए कोई संदेह नहीं है --- यह वास्तव में बहुत सुलभ और पठनीय है। यदि इसकी लंबाई के लिए नहीं, तो यह शायद यहां एक उत्तर के रूप में फिट होगा!
तिखन जेल्विस

शायद इस जवाब को समझने के लिए, ह्यूजेस को एक पेपर पढ़ना चाहिए ... ऐसा नहीं होने से, मैं अभी भी यह देखने में असफल हो रहा हूं कि आलस्य और न्यूनाधिकता कैसे और क्यों संबंधित है।
स्टैकक्स

@stakx एक बेहतर विवरण के बिना, वे संयोग से संबंधित नहीं लगते हैं। इस उदाहरण में आलस्य का लाभ यह है कि एक आलसी जनरेटर खेल के सभी संभव राज्यों को उत्पन्न करने में सक्षम है, लेकिन ऐसा करने में समय / स्मृति बर्बाद नहीं हो रहा है, क्योंकि ऐसा होने वाले केवल उपभोग किए जाएंगे। जनरेटर को एक आलसी जनरेटर के बिना उपभोक्ता से अलग किया जा सकता है, और यह संभव है (यद्यपि अधिक कठिन) उपभोक्ता से अलग किए बिना आलसी होना।
इज़काता 18

@Iztaka: "जनरेटर को उपभोक्ता से अलग किया जा सकता है बिना आलसी जनरेटर के, और यह संभव है (यद्यपि अधिक कठिन) उपभोक्ता से अलग हुए बिना आलसी होना।" ध्यान दें कि जब आप इस मार्ग पर जाते हैं, तो आप एक ** अत्यधिक विशिष्ट जनरेटर ** के साथ समाप्त हो सकते हैं - जनरेटर को एक उपभोक्ता को अनुकूलित करने के लिए लिखा गया था, और जब इसे दूसरों के लिए पुन: उपयोग किया जाता है, तो यह सबॉप्टीमल है। एक सामान्य उदाहरण ऑब्जेक्ट-रिलेशनल मैपर्स है जो एक संपूर्ण ऑब्जेक्ट ग्राफ़ को सिर्फ इसलिए प्राप्त करते हैं और बनाते हैं क्योंकि आप रूट ऑब्जेक्ट से एक स्ट्रिंग चाहते हैं। आलस्य ऐसे कई मामलों से बचता है (लेकिन सभी नहीं)।
sacundim

32

इस प्रतिमान का उपयोग पूर्वानुमेय सॉफ़्टवेयर के निर्माण के लिए कैसे किया जा सकता है जो पहले से ही काम करता है, जब हमारे पास कोई गारंटी नहीं है कि एक अभिव्यक्ति का मूल्यांकन कब और कहाँ किया जाएगा?

जब कोई अभिव्यक्ति साइड-इफ़ेक्ट फ्री होती है, तो जिस क्रम में मूल्यांकन किया जाता है, वह उनके मूल्य को प्रभावित नहीं करता है, इसलिए प्रोग्राम का व्यवहार ऑर्डर से प्रभावित नहीं होता है। तो व्यवहार पूरी तरह से अनुमानित है।

अब साइड इफेक्ट एक अलग मामला है। यदि दुष्प्रभाव किसी भी क्रम में हो सकते हैं, तो कार्यक्रम का व्यवहार वास्तव में अप्रत्याशित होगा। लेकिन वास्तव में ऐसा नहीं है। हास्केल जैसी आलसी भाषाएं इसे संदर्भात्मक रूप से पारदर्शी बनाने का एक बिंदु बनाती हैं, अर्थात यह सुनिश्चित करती हैं कि जिस क्रम में भावों का मूल्यांकन किया जाता है वह कभी भी उनके परिणाम को प्रभावित नहीं करेगा। हास्केल में यह आईओ मोनड के अंदर होने वाले उपयोगकर्ता-दृश्य दुष्प्रभावों के साथ सभी कार्यों को मजबूर करके प्राप्त किया जाता है। यह सुनिश्चित करता है कि सभी साइड-इफेक्ट्स ठीक उसी क्रम में हो, जिस क्रम में आप उम्मीद करेंगे।


15
यही कारण है कि केवल हस्केल जैसे "लागू शुद्धता" वाली भाषाएं डिफ़ॉल्ट रूप से हर जगह आलस्य का समर्थन करती हैं। स्काला जैसी "प्रोत्साहित शुद्धता" भाषाओं को स्पष्ट रूप से यह कहने के लिए प्रोग्रामर की आवश्यकता होती है कि वे कहाँ आलस्य चाहते हैं, और यह सुनिश्चित करने के लिए प्रोग्रामर पर निर्भर है कि आलस्य समस्याओं का कारण नहीं होगा। एक भाषा जो डिफ़ॉल्ट रूप से आलस्य की थी और जिसके दुष्प्रभाव नहीं थे, वास्तव में अनुमानित रूप से प्रोग्राम करना मुश्किल होगा।
बेन

1
IO के अलावा निश्चित रूप से मोनाड भी दुष्प्रभाव पैदा कर सकते हैं
jk।

1
@jk केवल IO बाहरी दुष्प्रभाव पैदा कर सकता है ।
dave4420

@ dave4420 हाँ, लेकिन यह नहीं कि यह उत्तर क्या कहता है
jk।

1
@ जेक इन हास्केल वास्तव में नहीं। IO को छोड़कर किसी भी मोनाड (या IO पर बनने वाले) के दुष्प्रभाव नहीं हैं। और यह केवल इसलिए है क्योंकि कंपाइलर IO को अलग तरह से मानता है। यह IO को "अपरिवर्तनीय बंद" के रूप में मानता है। एक विशिष्ट निष्पादन क्रम सुनिश्चित करने के लिए मोनाड सिर्फ एक (चतुर) तरीका है (ताकि उपयोगकर्ता "हाँ" दर्ज करने के बाद आपकी फ़ाइल केवल हटा दी जाएगी )।
स्कारफ्रिज सेप

22

यदि आप डेटाबेस से परिचित हैं, तो डेटा को संसाधित करने का एक बहुत लगातार तरीका है:

  • जैसे प्रश्न पूछें select * from foobar
  • जबकि अधिक डेटा है, क्या करें: परिणामों की अगली पंक्ति प्राप्त करें और इसे संसाधित करें

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

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

(जावा प्रोग्रामर Iterators के साथ इस व्यवहार का अनुकरण कर सकते हैं - अन्य भाषाओं में कुछ समान हो सकता है)


अच्छी बात। उदाहरण के लिए Collection2.filter()(साथ ही उस वर्ग से अन्य तरीके) बहुत अधिक आलसी मूल्यांकन लागू करता है: परिणाम "एक सामान्य" जैसा दिखता है Collection, लेकिन निष्पादन का क्रम गैर-सहज (या कम से कम गैर-स्पष्ट) हो सकता है। इसके अलावा, yieldपायथन में है (और सी # में एक समान विशेषता, जिसे मैं नाम नहीं बताता हूं) जो एक सामान्य Iterator की तुलना में आलसी मूल्यांकन का समर्थन करने के करीब है।
जोकिम सॉर

@JoachimSauer सी # में इसकी पैदावार वापसी, या निश्चित रूप से आप पहले से तैयार लाइनक ऑप्रेटर्स का उपयोग कर सकते हैं, जिनमें से लगभग आधे आलसी
जेके हैं।

+1: अत्यावश्यक / वस्तु-उन्मुख भाषा में पुनरावृत्तियों का उल्लेख करने के लिए। मैंने जावा में स्ट्रीम और स्ट्रीम फ़ंक्शन को लागू करने के लिए एक समान समाधान का उपयोग किया। पुनरावृत्तियों का उपयोग करके मेरे पास अज्ञात लंबाई के इनपुट स्ट्रीम पर टेक (n), dropWhile () जैसे कार्य हो सकते हैं।
जियोर्जियो

13

पहले 2000 उपयोगकर्ताओं की सूची के लिए अपने डेटाबेस से पूछने पर विचार करें, जिनके नाम "Ab" से शुरू होते हैं और 20 वर्ष से अधिक पुराने हैं। साथ ही उन्हें पुरुष होना चाहिए।

यहाँ थोड़ा आरेख है।

You                                            Program Processor
------------------------------------------------------------------------------
Get the first 2000 users ---------->---------- OK!
                         --------------------- So I'll go get those records...
WAIT! Also, they have to ---------->---------- Gotcha!
start with "Ab"
                         --------------------- NOW I'll get them...
WAIT! Make sure they're  ---------->---------- Good idea Boss!
over 20!
                         --------------------- Let's go then...
And one more thing! Make ---------->---------- Anything else? Ugh!
sure they're male!

No that is all. :(       ---------->---------- FINE! Getting records!

                         --------------------- Here you go. 
Thanks Postgres, you're  ---------->----------  ...
my only friend.

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

जैसा कि पहले 2000 उपयोगकर्ताओं को प्राप्त करने का विरोध किया गया था, उन्हें वापस करते हुए, उन्हें "अब" के लिए फ़िल्टर करना, उन्हें वापस करना, उन्हें 20 से अधिक के लिए फ़िल्टर करना, उन्हें वापस करना और पुरुष के लिए फ़िल्टर करना और अंत में उन्हें वापस करना।

आलसी लोडिंग संक्षेप में।


1
यह एक बहुत ही घटिया स्पष्टीकरण है IMHO। दुर्भाग्य से मैं इसे वोट करने के लिए इस विशेष एसई साइट पर पर्याप्त प्रतिनिधि नहीं है। आलसी मूल्यांकन का वास्तविक बिंदु यह है कि इनमें से कोई भी परिणाम वास्तव में तब तक उत्पन्न नहीं होता है जब तक कि कोई और चीज उनके उपभोग के लिए तैयार न हो।
एल्निटैक

मेरा पोस्ट किया गया उत्तर आपकी टिप्पणी के समान सटीक बात कह रहा है।
नागरसेग

यह एक बहुत विनम्र प्रोग्राम प्रोसेसर है।
जूलियन

9

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

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

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


5

आलसी मूल्यांकन के लिए कई तर्क हैं जो मुझे लगता है कि मजबूर हैं

  1. मॉड्यूलर मूल्यांकन आलसी मूल्यांकन के साथ आप भागों में कोड को तोड़ सकते हैं। उदाहरण के लिए, मान लीजिए कि आपको सूची सूची में तत्वों के पहले दस पारस्परिक तत्वों को खोजने की समस्या है, जैसे कि पारस्परिक 1. से कम हैं। हास्केल जैसी किसी चीज में आप लिख सकते हैं

    take 10 . filter (<1) . map (1/)
    

    लेकिन यह केवल एक सख्त भाषा में गलत है, क्योंकि यदि आप इसे देते हैं तो [2,3,4,5,6,7,8,9,10,11,12,0]आप शून्य से विभाजित होंगे। यह अभ्यास में भयानक क्यों है, इसके लिए सैमुंडिम का उत्तर देखें

  2. अधिक चीजें सख्ती से काम करती हैं (सजा का उद्देश्य) अधिक कार्यक्रम सख्त मूल्यांकन के साथ गैर सख्त मूल्यांकन के साथ समाप्त होते हैं। यदि आपका कार्यक्रम "उत्सुक" मूल्यांकन रणनीति के साथ समाप्त होता है, तो यह एक "आलसी" के साथ समाप्त होगा, लेकिन ऑपोजिट सच नहीं है। आपको इस घटना के विशिष्ट उदाहरणों के रूप में अनंत डेटा संरचनाओं (जो वास्तव में केवल थोड़े शांत हैं) जैसी चीजें मिलती हैं। अधिक कार्यक्रम आलसी भाषाओं में काम करते हैं।

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

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


2

माना कि आपके पास ऑफ़र पर बहुत महंगी गणनाएँ हैं, लेकिन यह नहीं जानते कि वास्तव में किन लोगों की ज़रूरत होगी, या किस क्रम में। आप एक जटिल मदर-मे-आई प्रोटोकॉल जोड़ सकते हैं ताकि उपभोक्ता को यह पता लगाने के लिए मजबूर किया जा सके कि क्या उपलब्ध है और अभी तक नहीं की गई गणनाओं को ट्रिगर करें। या आप बस एक इंटरफ़ेस प्रदान कर सकते हैं जो कार्य करता है, हालांकि गणना सभी की गई थी।

इसके अलावा, मान लीजिए कि आपके पास एक अनंत परिणाम है। उदाहरण के लिए सभी primes का सेट। यह स्पष्ट है कि आप पहले से सेट की गणना नहीं कर सकते हैं, इसलिए प्रिम्स के डोमेन में किसी भी ऑपरेशन को आलसी होना चाहिए।


1

आलसी मूल्यांकन के साथ आप कोड निष्पादन के बारे में नियंत्रण नहीं खोते हैं, यह अभी भी बिल्कुल निर्धारक है। हालांकि इसके साथ उपयोग करना कठिन है।

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

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