मैं लंबे समय से सोच रहा था कि आलसी मूल्यांकन उपयोगी क्यों है। मुझे अभी तक किसी को भी इस तरह से समझाना नहीं है जो समझ में आता है; ज्यादातर यह "मुझ पर भरोसा" करने के लिए उबलते समाप्त होता है।
नोट: मेरा मतलब संस्मरण से नहीं है।
मैं लंबे समय से सोच रहा था कि आलसी मूल्यांकन उपयोगी क्यों है। मुझे अभी तक किसी को भी इस तरह से समझाना नहीं है जो समझ में आता है; ज्यादातर यह "मुझ पर भरोसा" करने के लिए उबलते समाप्त होता है।
नोट: मेरा मतलब संस्मरण से नहीं है।
जवाबों:
अधिकतर इसलिए क्योंकि यह अधिक कुशल हो सकता है - यदि उनका उपयोग नहीं किया जा रहा है तो मूल्यों की गणना करने की आवश्यकता नहीं है। उदाहरण के लिए, मैं एक फ़ंक्शन में तीन मान पास कर सकता हूं, लेकिन सशर्त अभिव्यक्तियों के अनुक्रम के आधार पर, वास्तव में केवल एक सबसेट का उपयोग किया जा सकता है। C जैसी भाषा में, तीनों मानों की गणना वैसे भी की जाएगी; लेकिन हास्केल में, केवल आवश्यक मूल्यों की गणना की जाती है।
यह अनंत सूचियों की तरह शांत सामान के लिए भी अनुमति देता है। C जैसी भाषा में मेरी कोई अनंत सूची नहीं हो सकती है, लेकिन हास्केल में, यह कोई समस्या नहीं है। गणित के कुछ क्षेत्रों में अनंत सूचियों का उपयोग अक्सर किया जाता है, इसलिए उनमें हेरफेर करने की क्षमता होना उपयोगी हो सकता है।
आलसी मूल्यांकन का एक उपयोगी उदाहरण है quickSort
:
quickSort [] = []
quickSort (x:xs) = quickSort (filter (< x) xs) ++ [x] ++ quickSort (filter (>= x) xs)
यदि हम अब सूची में न्यूनतम खोजना चाहते हैं, तो हम परिभाषित कर सकते हैं
minimum ls = head (quickSort ls)
जो पहले सूची को क्रमबद्ध करता है और फिर सूची का पहला तत्व लेता है। हालांकि, आलसी मूल्यांकन के कारण, केवल सिर की गणना की जाती है। उदाहरण के लिए, यदि हम सूची को न्यूनतम लेते हैं तो [2, 1, 3,]
क्विकॉर्ट पहले उन सभी तत्वों को फ़िल्टर करेगा जो दो से छोटे हैं। फिर यह उस पर क्विकॉर्ट करता है (सिंगलटन सूची [1] लौटाता है) जो पहले से ही पर्याप्त है। आलसी मूल्यांकन के कारण, बाकी को कभी हल नहीं किया जाता है, जिससे बहुत अधिक कम्प्यूटेशनल समय की बचत होती है।
यह बेशक एक बहुत ही सरल उदाहरण है, लेकिन आलसी कार्यक्रमों के लिए उसी तरह से काम करता है जो बहुत बड़े होते हैं।
हालांकि, इस सब के लिए एक नकारात्मक पहलू है: आपके प्रोग्राम की रनटाइम गति और मेमोरी उपयोग की भविष्यवाणी करना कठिन हो जाता है। इसका मतलब यह नहीं है कि आलसी कार्यक्रम धीमे होते हैं या अधिक मेमोरी लेते हैं, लेकिन यह जानना अच्छा है।
take k $ quicksort list
केवल O (n + k log k) समय लगता है, जहां n = length list
। एक गैर-आलसी तुलना प्रकार के साथ, यह हमेशा O (n लॉग एन) समय लेगा।
मुझे आलसी मूल्यांकन कई चीजों के लिए उपयोगी लगता है।
सबसे पहले, सभी मौजूदा आलसी भाषाएं शुद्ध हैं, क्योंकि एक आलसी भाषा में दुष्प्रभाव के बारे में तर्क करना बहुत कठिन है।
शुद्ध भाषाएं आपको तर्क संगत तर्क का उपयोग करके फ़ंक्शन परिभाषा के बारे में बताती हैं।
foo x = x + 3
दुर्भाग्य से एक गैर-आलसी सेटिंग में, अधिक कथन एक आलसी सेटिंग की तुलना में वापस जाने में विफल होते हैं, इसलिए यह एमएल जैसी भाषाओं में कम उपयोगी है। लेकिन एक आलसी भाषा में आप सुरक्षित रूप से समानता का कारण बन सकते हैं।
दूसरी बात, एमएल में 'वैल्यू प्रतिबंध' जैसी बहुत सारी चीजें हस्केल जैसी आलसी भाषाओं में आवश्यक नहीं हैं। इससे सिंटैक्स की बड़ी गिरावट होती है। एमएल जैसी भाषाओं को var या fun जैसे कीवर्ड का उपयोग करने की आवश्यकता होती है। हास्केल में ये चीजें एक धारणा तक गिर जाती हैं।
तीसरा, आलस्य आपको बहुत कार्यात्मक कोड लिखने देता है जिसे टुकड़ों में समझा जा सकता है। हास्केल में यह एक फंक्शन बॉडी लिखना आम है:
foo x y = if condition1
then some (complicated set of combinators) (involving bigscaryexpression)
else if condition2
then bigscaryexpression
else Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
यह आपको एक फ़ंक्शन के शरीर की समझ के अनुसार 'टॉप डाउन' काम करने देता है। एमएल जैसी भाषाएं आपको let
कड़ाई से मूल्यांकन करने के लिए मजबूर करती हैं । नतीजतन, आप फ़ंक्शन के मुख्य निकाय को 'क्लॉज़' करने का साहस नहीं करते हैं, क्योंकि यदि यह महंगा है (या इसके साइड इफेक्ट्स हैं) तो आप नहीं चाहते कि इसका हमेशा मूल्यांकन किया जाए। हास्केल विवरण को स्पष्ट रूप से उस खंड में 'पुश' कर सकते हैं जहां यह स्पष्ट रूप से है क्योंकि यह जानता है कि उस खंड की सामग्री का केवल आवश्यकतानुसार मूल्यांकन किया जाएगा।
व्यवहार में, हम गार्ड का उपयोग करते हैं और इसे आगे पतन करते हैं:
foo x y
| condition1 = some (complicated set of combinators) (involving bigscaryexpression)
| condition2 = bigscaryexpression
| otherwise = Nothing
where some x y = ...
bigscaryexpression = ...
condition1 = ...
condition2 = ...
चौथा, आलस्य कभी-कभी कुछ एल्गोरिदम की अधिक सुरुचिपूर्ण अभिव्यक्ति प्रदान करता है। हास्केल में एक आलसी 'त्वरित प्रकार' एक-लाइनर है और इसका लाभ यह है कि यदि आप केवल पहले कुछ वस्तुओं को देखते हैं, तो आप केवल उन वस्तुओं के चयन की लागत के लिए आनुपातिक भुगतान करते हैं। कुछ भी आपको ऐसा करने से सख्ती से रोकता है, लेकिन आपको एक ही समानार्थी प्रदर्शन प्राप्त करने के लिए हर बार एल्गोरिदम को फिर से भरना होगा।
पांचवां, आलस्य आपको भाषा में नई नियंत्रण संरचनाओं को परिभाषित करने की अनुमति देता है। आप एक सख्त भाषा में निर्माण की तरह एक नया 'अगर .. तो .. और ..' नहीं लिख सकते। यदि आप किसी फ़ंक्शन को परिभाषित करने का प्रयास करते हैं जैसे:
if' True x y = x
if' False x y = y
एक सख्त भाषा में तो दोनों शाखाओं का मूल्यांकन किया जाएगा, भले ही हालत मूल्य की परवाह किए बिना। जब आप लूप पर विचार करते हैं तो यह खराब हो जाता है। सभी सख्त समाधानों के लिए भाषा की आवश्यकता होती है ताकि आपको किसी प्रकार का उद्धरण या स्पष्ट लंबोदर निर्माण प्रदान किया जा सके।
अंत में, उसी नस में, टाइप सिस्टम में दुष्प्रभावों से निपटने के लिए कुछ बेहतरीन तंत्र, जैसे कि मोनड्स, वास्तव में केवल एक आलसी सेटिंग में प्रभावी रूप से व्यक्त किया जा सकता है। इसे हास्केल मोनाड्स के एफ # वर्कफ़्लोज़ की जटिलता की तुलना करके देखा जा सकता है। (आप एक सख्त भाषा में एक मठ को परिभाषित कर सकते हैं, लेकिन दुर्भाग्य से आप अक्सर एक टन सख्त सामान का एक टन लेने की तुलना में आलस्य और वर्कफ़्लोज़ की कमी के कारण एक मठ कानून या दो को विफल कर देंगे।)
let
एक खतरनाक जानवर है, R6RS योजना में यह #f
आपके कार्यकाल में यादृच्छिक रूप से प्रकट होता है जहाँ गाँठ को सख्ती से बांधने से एक चक्र होता है! कोई सज़ा का इरादा नहीं है, लेकिन let
एक आलसी भाषा में सख्ती से अधिक पुनरावर्ती बाइंडिंग समझदार हैं। कठोरता भी इस तथ्य को बढ़ाती है कि where
एससीसी को छोड़कर किसी भी तरह के सापेक्ष प्रभाव का आदेश देने का कोई तरीका नहीं है, यह एक बयान स्तर का निर्माण है, इसका प्रभाव किसी भी क्रम में सख्ती से हो सकता है, और यहां तक कि अगर आपके पास एक शुद्ध भाषा है जो आप के साथ हवा है। #f
मुद्दा। सख्त where
गैर-स्थानीय चिंताओं के साथ अपने कोड को कठोर करता है।
ifFunc(True, x, y)
दोनों का मूल्यांकन करने जा रहा है x
और y
सिर्फ के बजाय x
।
सामान्य आदेश मूल्यांकन में एक आलसी मूल्यांकन (हास्केल में) के बीच अंतर है।
square x = x * x
निम्नलिखित अभिव्यक्ति का मूल्यांकन ...
square (square (square 2))
... उत्सुक मूल्यांकन के साथ:
> square (square (2 * 2))
> square (square 4)
> square (4 * 4)
> square 16
> 16 * 16
> 256
... सामान्य आदेश मूल्यांकन के साथ:
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * (square (square 2))
> ((2 * 2) * (square 2)) * (square (square 2))
> (4 * (square 2)) * (square (square 2))
> (4 * (2 * 2)) * (square (square 2))
> (4 * 4) * (square (square 2))
> 16 * (square (square 2))
> ...
> 256
... आलसी मूल्यांकन के साथ:
> (square (square 2)) * (square (square 2))
> ((square 2) * (square 2)) * ((square 2) * (square 2))
> ((2 * 2) * (2 * 2)) * ((2 * 2) * (2 * 2))
> (4 * 4) * (4 * 4)
> 16 * 16
> 256
ऐसा इसलिए है क्योंकि आलसी मूल्यांकन वाक्यविन्यास पेड़ को देखता है और वृक्ष-परिवर्तन करता है ...
square (square (square 2))
||
\/
*
/ \
\ /
square (square 2)
||
\/
*
/ \
\ /
*
/ \
\ /
square 2
||
\/
*
/ \
\ /
*
/ \
\ /
*
/ \
\ /
2
... जबकि सामान्य क्रम मूल्यांकन केवल शाब्दिक विस्तार करता है।
इसीलिए, जब हम आलसी मूल्यांकन का उपयोग करते हैं, तो अधिक शक्तिशाली हो जाते हैं (मूल्यांकन अधिक बार समाप्त हो जाता है तब अन्य रणनीतियां) जबकि प्रदर्शन उत्सुक मूल्यांकन (कम से कम ओ-नोटेशन में) के बराबर है।
सीपीयू से संबंधित आलसी मूल्यांकन उसी तरह है जैसे रैम से संबंधित कचरा संग्रह। जीसी आपको यह दिखावा करने की अनुमति देता है कि आपके पास असीमित मात्रा में मेमोरी है और इस प्रकार मेमोरी में जितनी जरूरत है उतनी वस्तुओं का अनुरोध करें। रनटाइम अनुपयोगी वस्तुओं को स्वचालित रूप से पुनः प्राप्त करेगा। LE आपको यह दिखावा करने की अनुमति देता है कि आपके पास असीमित कम्प्यूटेशनल संसाधन हैं - आप जितने चाहें उतने कम्प्यूटेशन कर सकते हैं। रनटाइम सिर्फ अनावश्यक (दिए गए मामले के लिए) संगणना नहीं करेगा।
इन "दिखावा" मॉडल का व्यावहारिक लाभ क्या है? यह संसाधनों के प्रबंधन से डेवलपर (कुछ हद तक) को मुक्त करता है और आपके स्रोतों से कुछ बॉयलरप्लेट कोड निकालता है। लेकिन अधिक महत्वपूर्ण यह है कि आप अपने समाधान का व्यापक संदर्भों में कुशलता से पुन: उपयोग कर सकते हैं।
कल्पना कीजिए कि आपके पास एस और संख्या एन की एक सूची है। आपको सूची एस से नंबर एन नंबर एम के निकटतम खोजने की आवश्यकता है। आपके पास दो संदर्भ हो सकते हैं: एकल एन और कुछ सूची एल ऑफ एन (एल में प्रत्येक एन के लिए ई। आप S में निकटतम M को देखते हैं)। यदि आप आलसी मूल्यांकन का उपयोग करते हैं, तो आप S को सॉर्ट कर सकते हैं और निकटतम M को N खोजने के लिए बाइनरी खोज लागू कर सकते हैं। अच्छी आलसी छँटाई के लिए इसे एकल N और O के लिए O (आकार (S)) चरणों की आवश्यकता होगी (ln (आकार (S)) * (आकार (एस) + आकार (एल)) समान रूप से वितरित एल के लिए कदम। यदि आपके पास प्रत्येक संदर्भ के लिए एल्गोरिदम को लागू करने के लिए इष्टतम दक्षता प्राप्त करने के लिए आलसी मूल्यांकन नहीं है।
यदि आपको लगता है कि साइमन पेयटन जोन्स, आलसी मूल्यांकन प्रति से महत्वपूर्ण नहीं है, लेकिन केवल एक 'बाल शर्ट' के रूप में, जिसने डिजाइनरों को भाषा को शुद्ध रखने के लिए मजबूर किया। मैं खुद को इस दृष्टिकोण से सहानुभूति पाता हूं।
रिचर्ड बर्ड, जॉन ह्यूजेस, और कुछ हद तक राल्फ हिनजे आलसी मूल्यांकन के साथ अद्भुत काम करने में सक्षम हैं। उनके काम को पढ़ने से आपको इसकी सराहना करने में मदद मिलेगी। अच्छे शुरुआती बिंदु के लिए बर्ड के शानदार सुडोकू सॉल्वर और ह्यूजेस के पेपर पर क्यों कार्यात्मक प्रोग्रामिंग मैटर्स हैं ।
IO
सन्यासी के परिचय से पहले ) का हस्ताक्षर main
होगा String -> String
और आप पहले से ही ठीक से इंटरेक्टिव प्रोग्राम लिख सकते थे।
IO
?
टिक-टैक-टो कार्यक्रम पर विचार करें। इसके चार कार्य हैं:
यह चिंताओं का एक अच्छा स्पष्ट अलगाव बनाता है। विशेष रूप से मूव-जनरेशन फंक्शन और बोर्ड मूल्यांकन फ़ंक्शंस केवल खेल के नियमों को समझने की आवश्यकता है: मूव ट्री और मिनिमैक्स फ़ंक्शंस पूरी तरह से पुन: प्रयोज्य हैं।
अब टिक-टैक-टो की बजाय शतरंज को लागू करने का प्रयास करें। "उत्सुक" (यानी पारंपरिक) भाषा में यह काम नहीं करेगा क्योंकि चाल का पेड़ स्मृति में फिट नहीं होगा। इसलिए अब बोर्ड के मूल्यांकन और चाल पीढ़ी कार्यों को चाल के पेड़ और न्यूनतम तर्क के साथ मिलाया जाना चाहिए क्योंकि न्यूनतम चाल तर्क का उपयोग यह तय करने के लिए किया जाना चाहिए कि कौन सी चालें उत्पन्न होती हैं। हमारी अच्छी साफ मॉड्यूलर संरचना गायब हो जाती है।
हालाँकि एक आलसी भाषा में मूव ट्री के तत्व केवल मिनिमैक्स फंक्शन से माँगों के जवाब में उत्पन्न होते हैं: पूरे एलिमेंट ट्री को उत्पन्न करने की आवश्यकता नहीं होती, इससे पहले कि हम शीर्ष तत्व पर मिनिमैक्स को ढीला छोड़ दें। इसलिए हमारी स्वच्छ मॉड्यूलर संरचना अभी भी एक वास्तविक खेल में काम करती है।
यहां दो और बिंदु दिए गए हैं, जो मुझे नहीं लगता कि चर्चा में लाया गया है।
आलस्य एक समवर्ती वातावरण में एक सिंक्रनाइज़ेशन तंत्र है। यह कुछ गणना के संदर्भ बनाने के लिए एक हल्का और आसान तरीका है, और कई थ्रेड्स के बीच इसके परिणामों को साझा करता है। यदि कई थ्रेड एक अनवैल्युएटेड वैल्यू तक पहुँचने का प्रयास करते हैं, तो उनमें से केवल एक ही इसे निष्पादित करेगा, और दूसरे एक बार उपलब्ध होने पर वैल्यू प्राप्त करते हुए, उसी के अनुसार ब्लॉक करेंगे।
आलस्य एक शुद्ध सेटिंग में डेटा संरचनाओं को परिशोधन करने के लिए मौलिक है। इसका वर्णन ओकासाकी द्वारा विशुद्ध रूप से कार्यात्मक डेटा संरचनाओं में विस्तार से किया गया है, लेकिन मूल विचार यह है कि आलसी मूल्यांकन उत्परिवर्तन का एक नियंत्रित रूप है जो हमें कुछ प्रकार के डेटा संरचनाओं को कुशलतापूर्वक लागू करने की अनुमति देता है। जबकि हम अक्सर आलस्य की बात करते हैं कि हमें पवित्रता की नाई पहनने के लिए मजबूर किया जाता है, दूसरा तरीका भी लागू होता है: वे एक समान भाषा की विशेषताएं हैं।
जब आप अपने कंप्यूटर और विंडोज को चालू करते हैं, तो अपनी हार्ड ड्राइव पर हर एक निर्देशिका को विंडोज एक्सप्लोरर में खोलने से रोकते हैं और आपके कंप्यूटर पर स्थापित हर एक प्रोग्राम को लॉन्च करने से मना करते हैं, जब तक कि आप यह संकेत नहीं देते कि एक निश्चित निर्देशिका की जरूरत है या एक निश्चित कार्यक्रम की जरूरत है, "आलसी" मूल्यांकन है।
"आलसी" मूल्यांकन जब और जिस तरह की जरूरत होती है, ऑपरेशन कर रहा है। यह उपयोगी है जब यह एक प्रोग्रामिंग भाषा या पुस्तकालय की एक विशेषता है क्योंकि यह आम तौर पर अपने आप पर आलसी मूल्यांकन को लागू करने के लिए कठिन है बस सब कुछ सामने से पहले करना।
इस पर विचार करो:
if (conditionOne && conditionTwo) {
doSomething();
}
विधि doSomething () केवल तभी निष्पादित होगी जब conditionOne सत्य है और conditionTwo सत्य है। ऐसी स्थिति में जहां conditionOne गलत है, आपको conditionTwo के परिणाम की गणना करने की आवश्यकता क्यों है? कंडीशनट्वो का मूल्यांकन इस मामले में समय की बर्बादी होगी, खासकर यदि आपकी स्थिति किसी विधि प्रक्रिया का परिणाम है।
यह आलसी मूल्यांकन ब्याज का एक उदाहरण है ...
यह दक्षता को बढ़ावा दे सकता है। यह स्पष्ट दिखने वाला है, लेकिन यह वास्तव में सबसे महत्वपूर्ण नहीं है। (यह भी ध्यान दें कि आलस्य दक्षता को भी मार सकता है - यह तथ्य तुरंत स्पष्ट नहीं है। हालांकि, बहुत सारे अस्थायी परिणामों को तुरंत गणना करने के बजाय, आप बड़ी मात्रा में रैम का उपयोग कर सकते हैं।)
यह आपको सामान्य उपयोगकर्ता-स्तरीय कोड में प्रवाह नियंत्रण निर्माणों को परिभाषित करने देता है, बजाय इसके कि इसे भाषा में हार्ड-कोड किया जाए। (जैसे, जावा में for
लूप्स हैं; हास्केल का एक for
कार्य है। जावा में अपवाद हैंडलिंग है; हास्केल में विभिन्न प्रकार के अपवाद मोनड हैं। सी # है goto
; हास्केल में निरंतरता मोनड है ...)
यह आपको एल्गोरिदम से डेटा उत्पन्न करने के लिए एल्गोरिथ्म को डिकूप करने देता है, यह तय करने के लिए कि कितना डेटा उत्पन्न करना है। आप एक ऐसा फ़ंक्शन लिख सकते हैं जो परिणामों की एक असाधारण-अनंत सूची तैयार करता है, और एक अन्य फ़ंक्शन जो इस सूची को उतना ही संसाधित करता है जितना वह इसकी आवश्यकता को तय करता है। बिंदु से अधिक, आपके पास पांच हो सकते हैं जनरेटर फ़ंक्शन और पांच उपभोक्ता फ़ंक्शन हो सकते हैं, और आप कुशलता से किसी भी संयोजन का उत्पादन कर सकते हैं - मैन्युअल रूप से कोडिंग के बजाय 5 x 5 = 25 फ़ंक्शन जो एक बार में दोनों कार्यों को जोड़ते हैं। (!) हम सभी जानते हैं कि decoupling एक अच्छी बात है।
यह कम या ज्यादा आपको एक शुद्ध कार्यात्मक भाषा डिजाइन करने के लिए मजबूर करता है। यह हमेशा शॉर्ट-कट लेने के लिए लुभाता है, लेकिन एक आलसी भाषा में, थोड़ी सी अशुद्धता आपके कोड को बेतहाशा अप्रत्याशित बना देती है, जो शॉर्टकट लेने के खिलाफ दृढ़ता से संघर्ष करता है।
आलस्य का एक बड़ा लाभ उचित परिशोधन सीमाओं के साथ अपरिवर्तनीय डेटा संरचनाओं को लिखने की क्षमता है। एक सरल उदाहरण एक अपरिवर्तनीय स्टैक (F # का उपयोग करके) है:
type 'a stack =
| EmptyStack
| StackNode of 'a * 'a stack
let rec append x y =
match x with
| EmptyStack -> y
| StackNode(hd, tl) -> StackNode(hd, append tl y)
कोड उचित है, लेकिन दो स्टैक x और y को जोड़ना O (x की लंबाई) सबसे अच्छा, सबसे खराब और औसत मामलों में समय लेता है। दो ढेर लगाना एक अखंड ऑपरेशन है, यह स्टैक एक्स में सभी नोड्स को छूता है।
हम डेटा संरचना को एक आलसी स्टैक के रूप में फिर से लिख सकते हैं:
type 'a lazyStack =
| StackNode of Lazy<'a * 'a lazyStack>
| EmptyStack
let rec append x y =
match x with
| StackNode(item) -> Node(lazy(let hd, tl = item.Force(); hd, append tl y))
| Empty -> y
lazy
इसके निर्माता में कोड के मूल्यांकन को निलंबित करके काम करता है। एक बार उपयोग करके मूल्यांकन करने के बाद .Force()
, वापसी मूल्य को कैश किया जाता है और प्रत्येक बाद में पुन: उपयोग किया जाता है.Force()
।
आलसी संस्करण के साथ, एपेंड एक ओ (1) ऑपरेशन हैं: यह 1 नोड लौटाता है और सूची के वास्तविक पुनर्निर्माण को निलंबित करता है। जब आप इस सूची के प्रमुख को प्राप्त करते हैं, तो यह नोड की सामग्री का मूल्यांकन करेगा, जिससे वह सिर को वापस कर देगा और शेष तत्वों के साथ एक निलंबन बना सकता है, इसलिए सूची का प्रमुख लेना O (1) ऑपरेशन है।
इसलिए, हमारी आलसी सूची पुनर्निर्माण की एक निरंतर स्थिति में है, आप इस सूची के पुनर्निर्माण की लागत का भुगतान तब तक नहीं करते हैं जब तक आप इसके सभी तत्वों के माध्यम से पार नहीं कर लेते। आलस्य का उपयोग करते हुए, यह सूची ओ (1) को समर्थन और जोड़ने का समर्थन करती है। दिलचस्प बात यह है कि जब से हम नोड्स का मूल्यांकन नहीं करते हैं, तब तक उनकी पहुंच पूरी तरह से संभावित अनंत तत्वों वाली सूची बनाने के लिए संभव नहीं है।
ऊपर दी गई डेटा संरचना में प्रत्येक ट्रैवर्सल पर नोड्स को फिर से विभाजित करने की आवश्यकता नहीं होती है, इसलिए वे .NET में वेनिला IEnumerables से अलग हैं।
यह स्निपेट आलसी और आलसी मूल्यांकन के बीच का अंतर दर्शाता है। बेशक इस रिटायरमेंट फंक्शन को अनुकूलित किया जा सकता है और पुनरावृत्ति के बजाय आलसी मूल्यांकन का उपयोग किया जा सकता है, लेकिन यह उदाहरण को खराब कर देगा।
मान लीजिए कि हम एम.ए. सभी 20 संख्या अग्रिम उत्पन्न होने की जरूरत नहीं आलसी मूल्यांकन के साथ, कुछ के लिए 20 प्रथम नंबरों का उपयोग करने के लिए है, लेकिन, आलसी मूल्यांकन के साथ वे के रूप में केवल जरूरत उत्पन्न हो जाएगा। इस प्रकार आप जरूरत पड़ने पर केवल गणना मूल्य का भुगतान करेंगे।
नमूना उत्पादन
आलसी पीढ़ी नहीं: 0.023373 आलसी पीढ़ी: 0.000009 आलसी आउटपुट नहीं: 0.000921 आलसी आउटपुट: 0.024205
import time
def now(): return time.time()
def fibonacci(n): #Recursion for fibonacci (not-lazy)
if n < 2:
return n
else:
return fibonacci(n-1)+fibonacci(n-2)
before1 = now()
notlazy = [fibonacci(x) for x in range(20)]
after1 = now()
before2 = now()
lazy = (fibonacci(x) for x in range(20))
after2 = now()
before3 = now()
for i in notlazy:
print i
after3 = now()
before4 = now()
for i in lazy:
print i
after4 = now()
print "Not lazy generation: %f" % (after1-before1)
print "Lazy generation: %f" % (after2-before2)
print "Not lazy output: %f" % (after3-before3)
print "Lazy output: %f" % (after4-before4)
आलसी मूल्यांकन डेटा संरचनाओं के साथ सबसे उपयोगी है। आप एक सरणी या वेक्टर को केवल संरचना में केवल कुछ बिंदुओं को निर्दिष्ट करने या पूरे सरणी के संदर्भ में अन्य सभी को व्यक्त करने के लिए एक सरणी को परिभाषित कर सकते हैं। यह आपको डेटा संरचनाओं को बहुत संक्षिप्त रूप से और उच्च रन-टाइम प्रदर्शन के साथ उत्पन्न करता है।
इस क्रिया को देखने के लिए, आप वृत्ति नामक मेरी तंत्रिका नेटवर्क लाइब्रेरी को देख सकते हैं । यह लालित्य और उच्च प्रदर्शन के लिए आलसी मूल्यांकन का भारी उपयोग करता है। उदाहरण के लिए मैं पारंपरिक रूप से अनिवार्य सक्रियण गणना से पूरी तरह से छुटकारा पा लेता हूं। एक साधारण आलसी अभिव्यक्ति मेरे लिए सब कुछ करती है।
यह सक्रियण फ़ंक्शन में उदाहरण के लिए और बैकप्रॉपैगैशन लर्निंग एल्गोरिथम में भी उपयोग किया जाता है (मैं केवल दो लिंक पोस्ट कर सकता हूं, इसलिए आपको मॉड्यूल learnPat
में फ़ंक्शन को देखने की आवश्यकता होगी AI.Instinct.Train.Delta
)। परंपरागत रूप से दोनों को बहुत अधिक जटिल पुनरावृत्ति एल्गोरिदम की आवश्यकता होती है।
अन्य लोगों ने पहले से ही सभी बड़े कारण दिए, लेकिन मुझे लगता है कि यह समझने में मदद करने के लिए एक उपयोगी अभ्यास क्यों है कि आलस्य एक कठोर भाषा में निश्चित-बिंदु फ़ंक्शन को लिखने और लिखने के लिए क्यों मायने रखता है ।
हास्केल में, एक निश्चित-बिंदु फ़ंक्शन सुपर आसान है:
fix f = f (fix f)
इसका विस्तार होता है
f (f (f ....
लेकिन क्योंकि हास्केल आलसी है, गणना की अनंत श्रृंखला कोई समस्या नहीं है; मूल्यांकन "बाहर-से-अंदर" किया जाता है, और सब कुछ अद्भुत तरीके से काम करता है:
fact = fix $ \f n -> if n == 0 then 1 else n * f (n-1)
महत्वपूर्ण रूप से, यह मायने नहीं रखता है कि fix
आलसी हो, बल्कि f
आलसी हो। एक बार जब आपको पहले से ही सख्त दिया जाता है f
, तो आप या तो अपने हाथों को हवा में फेंक सकते हैं और छोड़ सकते हैं, या एटा इसे विस्तारित कर सकता है और सामान को अव्यवस्थित कर सकता है। (यह बहुत कुछ है जैसा कि नूह इसके बारे में कह रहा था कि यह पुस्तकालय है जो सख्त / आलसी है, भाषा नहीं)।
अब कड़े स्काला में समान कार्य लिखने की कल्पना करें:
def fix[A](f: A => A): A = f(fix(f))
val fact = fix[Int=>Int] { f => n =>
if (n == 0) 1
else n*f(n-1)
}
आप निश्चित रूप से एक ढेर अतिप्रवाह प्राप्त करते हैं। यदि आप इसे काम करना चाहते हैं, तो आपको f
तर्क को कॉल-बाय-की जरूरत है:
def fix[A](f: (=>A) => A): A = f(fix(f))
def fact1(f: =>Int=>Int) = (n: Int) =>
if (n == 0) 1
else n*f(n-1)
val fact = fix(fact1)
मुझे नहीं पता कि आप वर्तमान में चीजों के बारे में कैसे सोचते हैं, लेकिन मुझे एक भाषा सुविधा के बजाय एक पुस्तकालय मुद्दे के रूप में आलसी मूल्यांकन के बारे में सोचना उपयोगी लगता है।
मेरा मतलब है कि सख्त भाषाओं में, मैं कुछ डेटा संरचनाओं का निर्माण करके आलसी मूल्यांकन को लागू कर सकता हूं, और आलसी भाषाओं (कम से कम हास्केल) में, जब मैं चाहता हूं, सख्ती से पूछ सकता हूं। इसलिए, भाषा का विकल्प वास्तव में आपके कार्यक्रमों को आलसी या गैर-आलसी नहीं बनाता है, लेकिन यह आपको प्रभावित करता है जो आपको डिफ़ॉल्ट रूप से मिलता है।
एक बार जब आप इसके बारे में ऐसा सोचते हैं, तो उन सभी स्थानों के बारे में सोचें, जहां आप एक डेटा संरचना लिखते हैं, जिसे आप बाद में डेटा उत्पन्न करने के लिए उपयोग कर सकते हैं (तब से पहले इसे बहुत अधिक देखे बिना), और आप आलसी के लिए बहुत सारे उपयोग देखेंगे। मूल्यांकन।
आलसी मूल्यांकन का सबसे उपयोगी शोषण जो मैंने उपयोग किया है वह एक ऐसा कार्य था जिसे किसी विशेष क्रम में उप-कार्यों की एक श्रृंखला कहा जाता है। यदि इन उप-कार्यों में से कोई भी विफल हो गया (झूठे वापस आ गया), कॉलिंग फ़ंक्शन को तुरंत वापस करने की आवश्यकता है। तो मैं इसे इस तरह से कर सकता था:
bool Function(void) {
if (!SubFunction1())
return false;
if (!SubFunction2())
return false;
if (!SubFunction3())
return false;
(etc)
return true;
}
या, अधिक सुरुचिपूर्ण समाधान:
bool Function(void) {
if (!SubFunction1() || !SubFunction2() || !SubFunction3() || (etc) )
return false;
return true;
}
एक बार जब आप इसका उपयोग करना शुरू कर देंगे, तो आपको इसे अधिक से अधिक बार उपयोग करने के अवसर दिखाई देंगे।
आलसी मूल्यांकन के बिना आपको ऐसा कुछ लिखने की अनुमति नहीं दी जाएगी:
if( obj != null && obj.Value == correctValue )
{
// do smth
}
अन्य बातों के अलावा, आलसी भाषाएँ बहुआयामी अनंत डेटा संरचनाओं की अनुमति देती हैं।
योजना, अजगर, आदि धाराओं के साथ एकल आयामी अनंत डेटा संरचनाओं की अनुमति देते हैं, आप केवल एक आयाम के साथ पार कर सकते हैं।
आलस्य एक ही फ्रिंज समस्या के लिए उपयोगी है , लेकिन यह उस लिंक में उल्लिखित coroutines कनेक्शन को ध्यान देने योग्य है।
आलसी मूल्यांकन गरीब आदमी के समतुल्य तर्क है (जो कि, आदर्श रूप से, प्रकार और संपत्तियों के गुणों से कोड के गुणों के समर्पण के लिए उम्मीद की जा सकती है)।
उदाहरण जहां यह काफी अच्छा काम करता है sum . take 10 $ [1..10000000000]
:। जिसे हम केवल एक प्रत्यक्ष और सरल संख्यात्मक गणना के बजाय 10 संख्याओं के योग से कम नहीं समझते हैं। बेशक आलसी मूल्यांकन के बिना यह सिर्फ अपने पहले 10 तत्वों का उपयोग करने के लिए स्मृति में एक विशाल सूची बनाएगा। यह निश्चित रूप से बहुत धीमी गति से होगा, और स्मृति में त्रुटि का कारण हो सकता है।
उदाहरण जहां यह उतना महान नहीं है जितना हम चाहेंगे sum . take 1000000 . drop 500 $ cycle [1..20]
:। जो वास्तव में 1 000 000 नंबरों का योग करेगा, भले ही एक सूची में एक लूप में हो; अभी भी इसे कुछ प्रत्यक्ष और कुछ सूत्रों के साथ, केवल एक प्रत्यक्ष संख्यात्मक गणना के लिए कम किया जाना चाहिए । कौन हैं एक बहुत बेहतर तो 1 000 000 संख्या संक्षेप हो। यहां तक कि अगर एक लूप में है, और सूची में नहीं है (यानी वनों की कटाई अनुकूलन के बाद)।
एक और बात है, यह पूंछ पुनरावृत्ति modulo विपक्ष शैली में कोड करना संभव बनाता है , और यह सिर्फ काम करता है ।
सीएफ संबंधित जवाब ।
यदि "आलसी मूल्यांकन" द्वारा आप का अर्थ है कंघी बुलियन में, जैसे में
if (ConditionA && ConditionB) ...
तो इसका उत्तर बस इतना है कि कम सीपीयू चक्र कार्यक्रम को खा जाता है, जितनी तेजी से यह चलेगा ... और यदि प्रसंस्करण निर्देशों के एक टुकड़े का कार्यक्रम के परिणाम पर कोई प्रभाव नहीं पड़ेगा तो यह आवश्यक है, (और इसलिए एक बेकार) समय का) उन्हें वैसे भी प्रदर्शन करने के लिए ...
अगर ओटोह, आपका मतलब है कि मुझे "आलसी इनिशियलाइज़र" के रूप में जाना जाता है, जैसे:
class Employee
{
private int supervisorId;
private Employee supervisor;
public Employee(int employeeId)
{
// code to call database and fetch employee record, and
// populate all private data fields, EXCEPT supervisor
}
public Employee Supervisor
{
get
{
return supervisor?? (supervisor = new Employee(supervisorId));
}
}
}
खैर, यह तकनीक ग्राहक कोड को पर्यवेक्षक डेटा रिकॉर्ड के लिए डेटाबेस को कॉल करने की आवश्यकता से बचने के लिए वर्ग का उपयोग करने की अनुमति देती है, जब कर्मचारी वस्तु का उपयोग करने वाले ग्राहक को पर्यवेक्षक के डेटा तक पहुंच की आवश्यकता होती है ... यह एक कर्मचारी को त्वरित करने की प्रक्रिया को तेज करता है। और फिर भी जब आपको पर्यवेक्षक की आवश्यकता होती है, तो पर्यवेक्षक संपत्ति पर पहला कॉल डेटाबेस कॉल को ट्रिगर करेगा और डेटा प्राप्त किया जाएगा और उपलब्ध होगा ...
आइए, 100,000 के तहत सबसे बड़ी संख्या ज्ञात करें जो कि 3829 से विभाज्य है। ऐसा करने के लिए, हम सिर्फ संभावनाओं का एक समूह फ़िल्टर करेंगे, जिसमें हम जानते हैं कि समाधान निहित है।
largestDivisible :: (Integral a) => a
largestDivisible = head (filter p [100000,99999..])
where p x = x `mod` 3829 == 0
हम सबसे पहले 100,000 से कम सभी संख्याओं की एक सूची बनाते हैं, नीचे उतरते हुए। फिर हम इसे अपने विधेय द्वारा फ़िल्टर करते हैं और क्योंकि संख्याएँ अवरोही तरीके से छांटी जाती हैं, जो सबसे बड़ी संख्या हमारे विधेय को संतुष्ट करती है वह फ़िल्टर की गई सूची का पहला तत्व है। हमें अपने शुरुआती सेट के लिए एक सीमित सूची का उपयोग करने की भी आवश्यकता नहीं थी। वह फिर से कार्रवाई में आलस्य है। क्योंकि हम केवल फ़िल्टर की गई सूची के प्रमुख का उपयोग करते हुए समाप्त होते हैं, इससे कोई फर्क नहीं पड़ता कि फ़िल्टर की गई सूची परिमित या अनंत है। पहला पर्याप्त समाधान मिलने पर मूल्यांकन रुक जाता है।