हस्केल पुनरावर्तन को लागू करने के लिए आलसी-मूल्यांकन का उपयोग करता है, इसलिए आवश्यकता होने पर मूल्य प्रदान करने के वादे के रूप में कुछ भी व्यवहार करता है (इसे एक थंक कहा जाता है)। चड्डी केवल आगे बढ़ने के लिए आवश्यक के रूप में कम हो जाती है, और नहीं। यह जिस तरह से आप एक अभिव्यक्ति को गणितीय रूप से सरल करता है, उससे मिलता जुलता है, इसलिए यह इस तरह से सोचने में मददगार है। तथ्य यह है कि मूल्यांकन आदेश आपके कोड द्वारा निर्दिष्ट नहीं किया जाता है, कंपाइलर केवल पूंछ-कॉल उन्मूलन की तुलना में बहुत अधिक चतुर अनुकूलन करने की अनुमति देता है। आप अनुकूलन चाहते हैं, तो संकलन करें -O2
!
आइए देखें कि हम facSlow 5
एक केस स्टडी के रूप में कैसे मूल्यांकन करते हैं:
facSlow 5
5 * facSlow 4
5 * (4 * facSlow 3)
5 * (4 * (3 * facSlow 2))
5 * (4 * (3 * (2 * facSlow 1)))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120
इसलिए जैसा कि आप चिंतित थे, किसी भी गणना के होने से पहले हमारे पास संख्याओं का निर्माण होता है, लेकिन विपरीत आप चिंतित हैं, वहाँ का कोई ढेर है facSlow
समाप्त करने के लिए इंतजार कर के आसपास फांसी समारोह कॉल - प्रत्येक कमी लागू किया जाता है और चला जाता है, एक छोड़ने स्टैक फ्रेम में अपनी वेक (क्योंकि (*)
यह सख्त है और इसलिए इसके दूसरे तर्क के मूल्यांकन को ट्रिगर करता है)।
हास्केल के पुनरावर्ती कार्यों का पुनरावर्ती तरीके से मूल्यांकन नहीं किया जाता है! चारों ओर लटकी हुई कॉलों का एकमात्र ढेर कई गुना है। अगर (*)
इसे सख्त डेटा कंस्ट्रक्टर के रूप में देखा जाता है, तो इसे पहरेदार पुनर्संरचना के रूप में जाना जाता है (हालांकि इसे आमतौर पर गैर- स्थैतिक डेटा कंस्ट्रक्टर के साथ संदर्भित किया जाता है , जहां इसके मद्देनजर क्या बचा है - डेटा कंस्ट्रक्टर हैं - जब आगे एक्सेस द्वारा मजबूर किया जाता है)।
अब आइए पूँछ-पुनरावर्ती को देखें fac 5
:
fac 5
fac' 5 1
fac' 4 {5*1}
fac' 3 {4*{5*1}}
fac' 2 {3*{4*{5*1}}}
fac' 1 {2*{3*{4*{5*1}}}}
{2*{3*{4*{5*1}}}}
(2*{3*{4*{5*1}}})
(2*(3*{4*{5*1}}))
(2*(3*(4*{5*1})))
(2*(3*(4*(5*1))))
(2*(3*(4*5)))
(2*(3*20))
(2*60)
120
तो आप देख सकते हैं कि कैसे पूंछ की पुनरावृत्ति ने आपको किसी भी समय या स्थान को बचाया नहीं है। इतना ही नहीं यह समग्र रूप से अधिक कदम उठाता हैfacSlow 5
, बल्कि यह एक नेस्टेड थंक (जैसा कि यहां दिखाया गया है {...}
) बनाता है - इसके लिए एक अतिरिक्त स्थान की आवश्यकता होती है - जो भविष्य की गणना, प्रदर्शन किए जाने वाले नेस्टेड गुणा का वर्णन करता है।
इस थंक को तब नीचे की ओर लाकर , स्टैक पर अभिकलन को फिर से जोड़कर, हटा दिया जाता है। दोनों संस्करणों के लिए बहुत लंबी गणनाओं के साथ स्टैक ओवरफ्लो पैदा करने का एक खतरा यहां भी है।
अगर हम इसे हाथ से ऑप्टिमाइज़ करना चाहते हैं, तो हमें बस इतना ही करना है। आप सख्त एप्लिकेशन ऑपरेटर का उपयोग कर सकते हैं$!
परिभाषित करने के का हैं
facSlim :: (Integral a) => a -> a
facSlim x = facS' x 1 where
facS' 1 y = y
facS' x y = facS' (x-1) $! (x*y)
यह बल facS'
अपने दूसरे तर्क में सख्त होने के लिए करता है। (यह अपने पहले तर्क में पहले से ही सख्त है क्योंकि यह तय करने के लिए मूल्यांकन किया जाना है कि किस परिभाषा facS'
को लागू करना है।)
कभी-कभी सख्ती बहुत मदद कर सकती है, कभी-कभी यह एक बड़ी गलती है क्योंकि आलस्य अधिक कुशल है। यहाँ यह एक अच्छा विचार है:
facSlim 5
facS' 5 1
facS' 4 5
facS' 3 20
facS' 2 60
facS' 1 120
120
मुझे लगता है कि आप क्या हासिल करना चाहते थे।
सारांश
- यदि आप अपने कोड को ऑप्टिमाइज़ करना चाहते हैं, तो चरण एक को संकलित करना है
-O2
- जब कोई थंक बिल्ड-अप नहीं होता है, तो टेल रीसर्शन केवल अच्छा होता है, और सख्ती जोड़ने से आमतौर पर इसे रोकने में मदद मिलती है, अगर और जहां उपयुक्त हो। ऐसा तब होता है जब आप एक परिणाम का निर्माण कर रहे होते हैं जिसकी आवश्यकता बाद में एक साथ होती है।
- कभी-कभी पूंछ पुनरावृत्ति एक बुरी योजना है और पहरा पुनरावृत्ति एक बेहतर फिट है, यानी जब आप निर्माण कर रहे हैं तो बिट्स द्वारा बिट्स की आवश्यकता होगी। देखें इस सवाल के बारे में
foldr
औरfoldl
उदाहरण के लिए, और उन्हें एक दूसरे के खिलाफ परीक्षण करें।
इन दोनों का प्रयास करें:
length $ foldl1 (++) $ replicate 1000
"The size of intermediate expressions is more important than tail recursion."
length $ foldr1 (++) $ replicate 1000
"The number of reductions performed is more important than tail recursion!!!"
foldl1
पूंछ पुनरावर्ती है, जबकि foldr1
संरक्षित पुनरावर्तन करता है ताकि आगे की प्रक्रिया / पहुंच के लिए पहले आइटम को तुरंत प्रस्तुत किया जाए। (एक बार बाईं ओर पहला "कोष्ठक" (...((s+s)+s)+...)+s
, इसकी इनपुट सूची को पूरी तरह से समाप्त करने के लिए मजबूर करना और भविष्य के संगणना का एक बड़ा हिस्सा बनाने की तुलना में इसके पूर्ण परिणाम की आवश्यकता है जितनी जल्दी हो सके; दूसरा कोष्ठक धीरे-धीरे दाईं ओर s+(s+(...+(s+s)...))
इनपुट का उपभोग करता है; बिट को थोड़ा सा सूचीबद्ध करें, इसलिए पूरी चीज अनुकूलन के साथ निरंतर स्थान में संचालित करने में सक्षम है)।
आपको उस हार्डवेयर के आधार पर शून्य की संख्या को समायोजित करने की आवश्यकता हो सकती है जो आप उपयोग कर रहे हैं।