ये अन्य उत्तर कुछ भ्रामक हैं। मैं मानता हूं कि वे कार्यान्वयन विवरण देते हैं जो इस असमानता की व्याख्या कर सकते हैं, लेकिन वे मामले से आगे निकल जाते हैं। जैसा कि सही ढंग से जेमाइट द्वारा सुझाया गया है, वे कार्यान्वयन-उन्मुख हैं फ़ंक्शन कॉल / पुनरावृत्ति के टूटे हुए कार्यान्वयन की । कई भाषाएं लय के माध्यम से लूप को लागू करती हैं, इसलिए लूप स्पष्ट रूप से उन भाषाओं में तेजी से नहीं जा रहे हैं। पुनरावर्तन सिद्धांत में लूपिंग (जब दोनों लागू होते हैं) की तुलना में कम कुशल नहीं है। मुझे गाइ स्टील के 1977 के पेपर के सार को "एक्सपेंसिव प्रोसीजर कॉल" मिथक से अवगत कराते हुए, मिथक या, प्रोसीजर इम्प्लीमेन्ट्स माने जाने वाले हानिकारक या लैम्बडा: अल्टीमेट गोटो
लोककथाओं में कहा गया है कि गोटो बयान "सस्ते" हैं, जबकि प्रक्रिया कॉल "महंगी" हैं। यह मिथक काफी हद तक खराब तरीके से तैयार किए गए भाषा कार्यान्वयन का परिणाम है। इस मिथक का ऐतिहासिक विकास माना जाता है। सैद्धांतिक विचारों और मौजूदा कार्यान्वयन दोनों पर चर्चा की जाती है जो इस मिथक को खत्म करते हैं। यह दिखाया गया है कि प्रक्रिया कॉल का अप्रतिबंधित उपयोग महान शैलीगत स्वतंत्रता की अनुमति देता है। विशेष रूप से, किसी भी फ्लोचार्ट को "संरचित" प्रोग्राम के रूप में लिखा जा सकता है, बिना अतिरिक्त चर पेश किए। गोटो बयान और प्रक्रिया कॉल के साथ कठिनाई अमूर्त प्रोग्रामिंग अवधारणाओं और ठोस भाषा निर्माणों के बीच संघर्ष के रूप में विशेषता है।
"अमूर्त प्रोग्रामिंग अवधारणाओं और ठोस भाषा निर्माणों के बीच संघर्ष" को इस तथ्य से देखा जा सकता है कि सबसे सैद्धांतिक मॉडल, उदाहरण के लिए, अनकैप्ड लैम्ब्डा कैलकुलस , एक स्टैक नहीं है । बेशक, यह संघर्ष आवश्यक नहीं है, क्योंकि उपरोक्त कागज दिखाता है और जैसा कि उन भाषाओं द्वारा भी दिखाया गया है जिनके पास हस्केल जैसे पुनरावृत्ति के अलावा कोई पुनरावृत्ति तंत्र नहीं है।
fix
fix f x = f (fix f) x
( λ x । एम) एन⇝ एम[ एन/ x][ एन/ x]एक्समएन⇝
अब एक उदाहरण के लिए। के fact
रूप में परिभाषित करें
fact = fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) 1
यहाँ के मूल्यांकन है fact 3
जहां, सघनता के लिए, मैं का उपयोग करेंगे, g
के लिए पर्याय के रूप में fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1))
, यानी fact = g 1
। यह मेरे तर्क को प्रभावित नहीं करता है।
fact 3
~> g 1 3
~> fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) 1 3
~> (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) g 1 3
~> (λa.λn.if n == 0 then a else g (a*n) (n-1)) 1 3
~> (λn.if n == 0 then 1 else g (1*n) (n-1)) 3
~> if 3 == 0 then 1 else g (1*3) (3-1)
~> g (1*3) (3-1)
~> g 3 2
~> fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) 3 2
~> (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) g 3 2
~> (λa.λn.if n == 0 then a else g (a*n) (n-1)) 3 2
~> (λn.if n == 0 then 3 else g (3*n) (n-1)) 2
~> if 2 == 0 then 3 else g (3*2) (2-1)
~> g (3*2) (2-1)
~> g 6 1
~> fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) 6 1
~> (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) g 6 1
~> (λa.λn.if n == 0 then a else g (a*n) (n-1)) 6 1
~> (λn.if n == 0 then 6 else g (6*n) (n-1)) 1
~> if 1 == 0 then 6 else g (6*1) (1-1)
~> g (6*1) (1-1)
~> g 6 0
~> fix (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) 6 0
~> (λf.λa.λn.if n == 0 then a else f (a*n) (n-1)) g 6 0
~> (λa.λn.if n == 0 then a else g (a*n) (n-1)) 6 0
~> (λn.if n == 0 then 6 else g (6*n) (n-1)) 0
~> if 0 == 0 then 6 else g (6*0) (0-1)
~> 6
आप बिना विवरण देखे भी आकृति से देख सकते हैं कि कोई वृद्धि नहीं है और प्रत्येक पुनरावृत्ति को उतनी ही जगह की आवश्यकता है। (तकनीकी रूप से, संख्यात्मक परिणाम बढ़ता है जो अपरिहार्य है और एक while
लूप के लिए सही है ।) मैं आपको यहां "बढ़ती" स्टैक को इंगित करने के लिए अवहेलना करता हूं।
ऐसा लगता है कि लैम्ब्डा कैलकुलस के पहले शब्द का अर्थ है "पूंछ कॉल ऑप्टिमाइज़ेशन" जो आमतौर पर गलत है। बेशक, यहां कोई "अनुकूलन" नहीं हो रहा है। "सामान्य" कॉल के विपरीत "पूंछ" कॉल के लिए यहां कोई विशेष नियम नहीं हैं। इस कारण से, "कॉल" ऑप्टिमाइज़ेशन क्या "कॉलिंग" ऑप्टिमाइज़ेशन का "अमूर्त" लक्षण वर्णन करना कठिन है, क्योंकि फंक्शन कॉल सिमेंटिक्स के कई अमूर्त लक्षण वर्णन में, टेल कॉल "ऑप्टिमाइज़ेशन" करने के लिए कुछ भी नहीं है!
की अनुरूप परिभाषा fact
कई भाषाओं में "स्टैक ओवरफ्लो" की एक , उन भाषाओं द्वारा फंक्शन कॉल सेमेंटिक्स को सही ढंग से लागू करने में विफलता है। (कुछ भाषाओं में एक बहाना है।) भाषा कार्यान्वयन के लिए स्थिति मोटे तौर पर एक समान है जो लिंक वाली सूचियों के साथ सरणियों को लागू करती है। ऐसे "सरणियों" में अनुक्रमण तब एक O (n) ऑपरेशन होगा जो सरणियों की अपेक्षा को पूरा नहीं करता है। यदि मैंने भाषा का एक अलग कार्यान्वयन किया, जो कि लिंक की गई सूचियों के बजाय वास्तविक सरणियों का उपयोग करता है, तो आप कहेंगे कि मैंने "सरणी एक्सेस ऑप्टिमाइज़ेशन" लागू नहीं किया है, तो आप कहेंगे कि मैंने सरणियों के टूटे कार्यान्वयन को ठीक किया।
तो, Veedrac के जवाब का जवाब। ढेर पुनरावृत्ति के लिए "मौलिक" नहीं हैं । इस हद तक कि "स्टैक-लाइक" व्यवहार मूल्यांकन के दौरान होता है, यह केवल उन मामलों में हो सकता है जहां लूप (एक सहायक डेटा संरचना के बिना) पहले स्थान पर लागू नहीं होगा! इसे दूसरे तरीके से रखने के लिए, मैं लूप्स को पुनरावृत्ति के साथ लागू कर सकता हूं, बिल्कुल उसी प्रदर्शन विशेषताओं के साथ। दरअसल, स्कीम और एसएमएल दोनों में लूपिंग कंस्ट्रक्शन होते हैं, लेकिन दोनों ही उन लोगों को रिकर्सन के संदर्भ में परिभाषित करते हैं (और, कम से कम स्कीम में, do
अक्सर को लागू किया एक मैक्रो के रूप में जाता है जो पुनरावर्ती कॉल में फैलता है।) इसी तरह, जोहान के जवाब के लिए, कुछ भी नहीं कहता है। संकलक को पुनरावृत्ति के लिए वर्णित असेंबली जोहान का उत्सर्जन करना चाहिए। वास्तव में, जाता है, बाध्य है असेंबली आप लूप्स या रिकर्सन का उपयोग करते हैं। केवल एक बार संकलक (कुछ) होगाजोहान का वर्णन करने के लिए विधानसभा के समान ही है जब आप कुछ ऐसा कर रहे हैं जो वैसे भी लूप द्वारा व्यक्त नहीं होता है। जैसा कि स्टील के पेपर में उल्लिखित है और हास्केल, स्कीम और एसएमएल जैसी भाषाओं के वास्तविक अभ्यास द्वारा प्रदर्शित किया गया है, यह "अत्यधिक दुर्लभ" नहीं है कि पूंछ कॉल को "अनुकूलित" किया जा सकता है, वे हमेशा कर सकते हैं "अनुकूलित" किया जा सकता है। क्या पुनरावृत्ति का एक विशेष उपयोग निरंतर स्थान पर चलेगा यह इस बात पर निर्भर करता है कि यह कैसे लिखा जाता है, लेकिन आपको जो संभव हो उसे लागू करने के लिए प्रतिबंधों की आवश्यकता होती है वे प्रतिबंध हैं जिन्हें आपको अपनी समस्या को लूप के आकार में फिट करने की आवश्यकता होगी। (वास्तव में, वे कम कड़े हैं। समस्याएं हैं, जैसे कि एन्कोडिंग स्टेट मशीन, जो कि अधिक स्पष्ट रूप से और कुशलता से टेल कॉल के माध्यम से नियंत्रित की जाती हैं, लूप के विपरीत, जिसके लिए सहायक चर की आवश्यकता होगी।) फिर से। लिए अधिक काम करने की आवश्यकता होती है। जब भी आपके कोड में एक लूप नहीं होता है।
मेरा अनुमान है कि जोहान सी संकलकों का जिक्र कर रहा है जिनके मनमाने ढंग से प्रतिबंध हैं जब वह पूंछ कॉल "अनुकूलन" करेगा। जोहान को संभवतः C ++ और Rust जैसी भाषाओं का उल्लेख है जब वह "प्रबंधित प्रकारों के साथ भाषाओं" के बारे में बात करता है। आरए II सी ++ से मुहावरा और जंग में मौजूद रूप में अच्छी तरह चीजें हैं जो ऊपरी तौर पर पूंछ कॉल, नहीं पूंछ कॉल की तरह लग रहे (क्योंकि "विनाशकर्ता" अभी भी जरूरत के नाम से जाना) बनाता है। कुछ अलग सिंटैक्स का उपयोग करने के लिए कुछ अलग सिमेंटिक्स का उपयोग करने के प्रस्ताव दिए गए हैं, जो पूंछ की पुनरावृत्ति (अर्थात् पहले विध्वंसक कॉल करने की अनुमति देगा)अंतिम पूंछ कॉल और स्पष्ट रूप से "नष्ट" वस्तुओं तक पहुंच को अस्वीकार करना)। (कचरा संग्रहण में ऐसा कोई मुद्दा नहीं है, और हास्केल, एसएमएल और योजना के सभी कचरा एकत्र की गई भाषाएं हैं।) एक बहुत अलग नस में, कुछ भाषाएं, जैसे कि स्मॉलटाकल, "स्टैक" को प्रथम श्रेणी की वस्तु के रूप में उजागर करती हैं, इनमें "स्टैक" अब एक कार्यान्वयन विवरण नहीं है, हालांकि यह अलग-अलग शब्दार्थों के साथ अलग-अलग प्रकार के कॉल को रोकता नहीं है। (जावा का कहना है कि यह सुरक्षा के कुछ पहलुओं को संभालने के तरीके के कारण नहीं हो सकता है, लेकिन यह वास्तव में गलत है ।)
व्यवहार में, फ़ंक्शन कॉल के टूटे हुए कार्यान्वयन का प्रसार तीन मुख्य कारकों से होता है। सबसे पहले, कई भाषाएं अपनी कार्यान्वयन भाषा (आमतौर पर सी) से टूटे हुए कार्यान्वयन को विरासत में लेती हैं। दूसरा, निर्धारक संसाधन प्रबंधन अच्छा है और इस मुद्दे को और अधिक जटिल बना देता है, हालांकि केवल कुछ मुट्ठी भर भाषाएं ही इसे पेश करती हैं। तीसरा, और, मेरे अनुभव में, ज्यादातर लोग इस बात की परवाह करते हैं, कि वे डीबगिंग उद्देश्यों के लिए त्रुटि होने पर स्टैक के निशान चाहते हैं। केवल दूसरा कारण एक है जो संभावित रूप से सैद्धांतिक रूप से प्रेरित हो सकता है।