जबकि लिस्प सीखना शुरू कर दिया है, मैं पूंछ-पुनरावर्ती शब्द पर आया हूं । इसका सही मतलब क्या है?
जबकि लिस्प सीखना शुरू कर दिया है, मैं पूंछ-पुनरावर्ती शब्द पर आया हूं । इसका सही मतलब क्या है?
जवाबों:
पहले N प्राकृतिक संख्याओं को जोड़ने वाले एक साधारण कार्य पर विचार करें। (जैसे sum(5) = 1 + 2 + 3 + 4 + 5 = 15
)।
यहाँ एक सरल जावास्क्रिप्ट कार्यान्वयन है जो पुनरावर्तन का उपयोग करता है:
function recsum(x) {
if (x === 1) {
return x;
} else {
return x + recsum(x - 1);
}
}
यदि आप कहते हैं recsum(5)
, तो यह है कि जावास्क्रिप्ट दुभाषिया मूल्यांकन करेगा:
recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
15
ध्यान दें कि जावास्क्रिप्ट दुभाषिया शुरू होने से पहले प्रत्येक पुनरावर्ती कॉल को कैसे पूरा करना है, वास्तव में राशि की गणना का काम करते हैं।
यहाँ एक ही फ़ंक्शन का टेल-पुनरावर्ती संस्करण है:
function tailrecsum(x, running_total = 0) {
if (x === 0) {
return running_total;
} else {
return tailrecsum(x - 1, running_total + x);
}
}
यहां उन घटनाओं का क्रम है जो आपको बुलाया जाता है tailrecsum(5)
, (जो tailrecsum(5, 0)
कि डिफ़ॉल्ट रूप से दूसरे तर्क के कारण प्रभावी रूप से होगा )।
tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15
पुनरावर्ती कॉल के प्रत्येक मूल्यांकन के साथ पूंछ-पुनरावर्ती मामले में, running_total
अद्यतन किया जाता है।
नोट: मूल उत्तर ने पायथन से उदाहरणों का उपयोग किया। इन्हें जावास्क्रिप्ट में बदल दिया गया है, क्योंकि पायथन दुभाषिए पूंछ कॉल अनुकूलन का समर्थन नहीं करते हैं । हालाँकि, जबकि टेल कॉल ऑप्टिमाइज़ेशन ECMAScript 2015 युक्ति का हिस्सा है , अधिकांश जावास्क्रिप्ट दुभाषिए इसका समर्थन नहीं करते हैं ।
tail recursion
कि ऐसी भाषा में कैसे प्राप्त किया जा सकता है जो टेल कॉल का अनुकूलन नहीं करती है।
में पारंपरिक प्रत्यावर्तन , विशिष्ट मॉडल है कि आप अपने पुनरावर्ती कॉल पहला प्रदर्शन, और फिर आप पुनरावर्ती कॉल के रिटर्न मान लेते हैं और परिणाम की गणना है। इस तरीके से, आपको अपनी गणना का परिणाम नहीं मिलता है जब तक कि आप हर पुनरावर्ती कॉल से वापस नहीं आए।
में पूंछ प्रत्यावर्तन , तो आप अपने गणना पहले करते हैं, और फिर आप पुनरावर्ती कॉल पर अमल, अगले पुनरावर्ती कदम के लिए अपने वर्तमान कदम के परिणाम गुजर। यह अंतिम विवरण के रूप में होता है (return (recursive-function params))
। मूल रूप से, किसी भी दिए गए पुनरावर्ती चरण का वापसी मूल्य अगले पुनरावर्ती कॉल के वापसी मूल्य के समान है ।
इसका परिणाम यह है कि एक बार जब आप अपने अगले पुनरावर्ती कदम के लिए तैयार हो जाते हैं, तो आपको वर्तमान स्टैक फ्रेम की आवश्यकता नहीं है। यह कुछ अनुकूलन के लिए अनुमति देता है। वास्तव में, एक उचित रूप से लिखे गए संकलक के साथ, आपको एक पूंछ पुनरावर्ती कॉल के साथ स्टैक ओवरफ्लो स्नीकर कभी नहीं होना चाहिए । बस अगले पुनरावर्ती चरण के लिए वर्तमान स्टैक फ्रेम का पुन: उपयोग करें। मुझे पूरा यकीन है कि लिस्प ऐसा करता है।
एक महत्वपूर्ण बिंदु यह है कि पूंछ की पुनरावृत्ति अनिवार्य रूप से लूपिंग के बराबर है। यह केवल कंपाइलर ऑप्टिमाइज़ेशन की बात नहीं है, बल्कि अभिव्यक्ति के बारे में एक बुनियादी तथ्य है। यह दोनों तरीके से जाता है: आप फॉर्म का कोई भी लूप ले सकते हैं
while(E) { S }; return Q
जहां E
और Q
अभिव्यक्ति हैं और S
बयानों का एक क्रम है, और इसे एक पूंछ पुनरावर्ती फ़ंक्शन में बदल दें
f() = if E then { S; return f() } else { return Q }
बेशक, E
, S
, और Q
कुछ चर पर कुछ दिलचस्प मूल्य की गणना करने के लिए परिभाषित किया जाना है। उदाहरण के लिए, लूपिंग फ़ंक्शन
sum(n) {
int i = 1, k = 0;
while( i <= n ) {
k += i;
++i;
}
return k;
}
टेल-पुनरावर्ती फ़ंक्शन (एस) के बराबर है
sum_aux(n,i,k) {
if( i <= n ) {
return sum_aux(n,i+1,k+i);
} else {
return k;
}
}
sum(n) {
return sum_aux(n,1,0);
}
(कम मापदंडों के साथ एक फ़ंक्शन के साथ पूंछ-पुनरावर्ती फ़ंक्शन का यह "रैपिंग" एक सामान्य कार्यात्मक मुहावरा है)
else { return k; }
बदला जा सकता हैreturn k;
लूआ में प्रोग्रामिंग बुक के इस अंश से पता चलता है कि एक उचित पूंछ पुनरावृत्ति कैसे करें (लूआ में, लेकिन लिस्प पर भी लागू होना चाहिए) और यह बेहतर क्यों है।
एक पूंछ कॉल [पूंछ पुनरावृत्ति] कॉल के रूप में तैयार की गई गोटो का एक प्रकार है। एक टेल कॉल तब होता है जब एक फ़ंक्शन दूसरे को अपनी अंतिम क्रिया के रूप में बुलाता है, इसलिए इसके पास और कुछ नहीं है। उदाहरण के लिए, निम्नलिखित कोड में, कॉल
g
टेल कॉल है:function f (x) return g(x) end
f
कॉल करने के बादg
, इसके पास और कुछ नहीं है। ऐसी स्थितियों में, प्रोग्राम को कॉलिंग फ़ंक्शन पर वापस जाने की आवश्यकता नहीं होती है जब तथाकथित फ़ंक्शन समाप्त होता है। इसलिए, पूंछ कॉल के बाद, प्रोग्राम को स्टैक में कॉलिंग फ़ंक्शन के बारे में कोई जानकारी रखने की आवश्यकता नहीं है। ...क्योंकि एक उचित टेल कॉल कोई स्टैक स्पेस का उपयोग नहीं करता है, "नेस्टेड" टेल कॉल की संख्या पर कोई सीमा नहीं है जो एक प्रोग्राम बना सकता है। उदाहरण के लिए, हम किसी भी संख्या के साथ निम्न फ़ंक्शन को तर्क कह सकते हैं; यह स्टैक को कभी भी ओवरफ्लो नहीं करेगा:
function foo (n) if n > 0 then return foo(n - 1) end end
... जैसा कि मैंने पहले कहा, टेल कॉल एक तरह का गोटो है। जैसे, लुआ में उचित पूंछ कॉल का एक काफी उपयोगी अनुप्रयोग प्रोग्रामिंग राज्य मशीनों के लिए है। इस तरह के अनुप्रयोग एक फ़ंक्शन द्वारा प्रत्येक राज्य का प्रतिनिधित्व कर सकते हैं; स्थिति बदलने के लिए (या कॉल करने के लिए) एक विशिष्ट समारोह में जाना है। एक उदाहरण के रूप में, आइए एक सरल भूलभुलैया गेम पर विचार करें। भूलभुलैया में कई कमरे हैं, जिनमें से प्रत्येक में चार दरवाजे हैं: उत्तर, दक्षिण, पूर्व और पश्चिम। प्रत्येक चरण पर, उपयोगकर्ता एक आंदोलन दिशा में प्रवेश करता है। यदि उस दिशा में एक दरवाजा है, तो उपयोगकर्ता संबंधित कमरे में जाता है; अन्यथा, प्रोग्राम एक चेतावनी प्रिंट करता है। प्रारंभिक कमरे से अंतिम कमरे तक जाने का लक्ष्य है।
यह खेल एक विशिष्ट राज्य मशीन है, जहां वर्तमान कमरा राज्य है। हम प्रत्येक कमरे के लिए एक फ़ंक्शन के साथ इस तरह के भूलभुलैया को लागू कर सकते हैं। हम एक कमरे से दूसरे कमरे में जाने के लिए टेल कॉल का उपयोग करते हैं। चार कमरों वाला एक छोटा भूलभुलैया इस तरह दिख सकता है:
function room1 () local move = io.read() if move == "south" then return room3() elseif move == "east" then return room2() else print("invalid move") return room1() -- stay in the same room end end function room2 () local move = io.read() if move == "south" then return room4() elseif move == "west" then return room1() else print("invalid move") return room2() end end function room3 () local move = io.read() if move == "north" then return room1() elseif move == "east" then return room4() else print("invalid move") return room3() end end function room4 () print("congratulations!") end
तो आप देखते हैं, जब आप एक पुनरावर्ती कॉल करते हैं जैसे:
function x(n)
if n==0 then return 0
n= n-2
return x(n) + 1
end
यह पुनरावर्ती नहीं है क्योंकि आपके पास पुनरावर्ती कॉल किए जाने के बाद भी उस फ़ंक्शन में चीजें (1 जोड़ने) हैं। यदि आप बहुत अधिक संख्या में इनपुट करते हैं तो यह संभवतः स्टैक ओवरफ्लो का कारण होगा।
नियमित पुनरावर्तन का उपयोग करते हुए, प्रत्येक पुनरावर्ती कॉल कॉल स्टैक पर एक और प्रविष्टि को धक्का देता है। जब पुनरावृत्ति पूरी हो जाती है, तो ऐप को प्रत्येक प्रविष्टि को वापस नीचे सभी तरह से पॉप करना होगा।
पूंछ पुनरावृत्ति के साथ, भाषा के आधार पर कंपाइलर एक प्रविष्टि के नीचे स्टैक को गिराने में सक्षम हो सकता है, जिससे आप स्टैक स्पेस को बचाते हैं ... एक बड़ी पुनरावर्ती क्वेरी वास्तव में स्टैक ओवरफ़्लो का कारण बन सकती है।
मूल रूप से टेल रिक्रिएशन को पुनरावृति में अनुकूलित किया जा सकता है।
इसे शब्दों से समझाने के बजाय, यहां एक उदाहरण दिया गया है। यह फैक्टरियल फ़ंक्शन का एक योजना संस्करण है:
(define (factorial x)
(if (= x 0) 1
(* x (factorial (- x 1)))))
यहाँ गुटबाजी का एक संस्करण है जो पूंछ-पुनरावर्ती है:
(define factorial
(letrec ((fact (lambda (x accum)
(if (= x 0) accum
(fact (- x 1) (* accum x))))))
(lambda (x)
(fact x 1))))
आप पहले संस्करण में देखेंगे कि पुनरावर्ती कॉल वास्तव में गुणन अभिव्यक्ति में खिलाया जाता है, और इसलिए पुनरावर्ती कॉल करते समय स्थिति को स्टैक पर सहेजा जाना है। टेल-पुनरावर्ती संस्करण में पुनरावर्ती कॉल के मूल्य की प्रतीक्षा में कोई अन्य एस-अभिव्यक्ति नहीं है, और चूंकि आगे कोई काम नहीं करना है, राज्य को स्टैक पर सहेजने की आवश्यकता नहीं है। एक नियम के रूप में, योजना पूंछ-पुनरावर्ती कार्य निरंतर स्टैक स्थान का उपयोग करते हैं।
list-reverse
प्रक्रिया निरंतर स्टैक स्पेस में चलेगी लेकिन ढेर पर डेटा संरचना बनाएगी और बढ़ेगी। एक ट्री ट्रैवरल एक अतिरिक्त तर्क में एक नकली स्टैक का उपयोग कर सकता है। आदि
पुनरावर्ती एल्गोरिथ्म में अंतिम पुनरावृत्ति निर्देश में टेल पुनरावृत्ति पुनरावर्ती कॉल को अंतिम रूप से संदर्भित करता है।
आमतौर पर पुनरावृत्ति में, आपके पास एक बेस-केस होता है जो कि पुनरावर्ती कॉल को रोकता है और कॉल स्टैक को पॉप करना शुरू करता है। क्लासिक उदाहरण का उपयोग करने के लिए, हालांकि लिस्प की तुलना में अधिक सी-ईश, फैक्टोरियल फ़ंक्शन पूंछ पुनरावृत्ति को दिखाता है। बेस-केस की स्थिति की जांच के बाद पुनरावर्ती कॉल होता है ।
factorial(x, fac=1) {
if (x == 1)
return fac;
else
return factorial(x-1, x*fac);
}
फैक्टरियल के लिए प्रारंभिक कॉल वह होगा factorial(n)
जहां fac=1
(डिफ़ॉल्ट मान) और n वह संख्या है जिसके लिए फैक्टरियल की गणना की जानी है।
else
वह चरण है जिसे आप "बेस केस" कह सकते हैं, लेकिन कई लाइनों में फैला हुआ है। क्या मैं आपको गलत समझ रहा हूं या मेरी धारणा सही है? पूंछ पुनरावृत्ति केवल एक लाइनर के लिए अच्छा है?
factorial
उदाहरण सिर्फ क्लासिक सरल उदाहरण सब है कि है,।
इसका मतलब यह है कि स्टैक पर निर्देश सूचक को धक्का देने की आवश्यकता के बजाय, आप बस एक पुनरावर्ती फ़ंक्शन के शीर्ष पर कूद सकते हैं और निष्पादन जारी रख सकते हैं। यह फ़ंक्शन को स्टैक को ओवरफ़्लो किए बिना अनिश्चित काल तक पुनरावृत्ति करने की अनुमति देता है।
मैंने इस विषय पर एक ब्लॉग पोस्ट लिखी है , जिसमें स्टैक फ्रेम की तरह दिखने वाले चित्रमय उदाहरण हैं।
यहां दो कार्यों की तुलना एक त्वरित कोड स्निपेट है। किसी दी गई संख्या के भाज्य को खोजने के लिए पहली पारंपरिक पुनरावृत्ति है। दूसरा पूंछ पुनरावृत्ति का उपयोग करता है।
समझने में बहुत सरल और सहज।
यह बताने का एक आसान तरीका है कि क्या एक पुनरावर्ती कार्य एक पूंछ पुनरावर्ती है यदि यह आधार मामले में एक ठोस मूल्य लौटाता है। मतलब यह है कि यह 1 या सही या ऐसा कुछ भी वापस नहीं करता है। यह विधि मापदंडों में से किसी एक के कुछ प्रकार की वापसी की संभावना से अधिक होगा।
एक और तरीका यह बताना है कि यदि पुनरावर्ती कॉल किसी भी अतिरिक्त, अंकगणित, संशोधन आदि से मुक्त है ... तो इसका एक शुद्ध पुनरावर्ती कॉल के अलावा कुछ भी नहीं है।
public static int factorial(int mynumber) {
if (mynumber == 1) {
return 1;
} else {
return mynumber * factorial(--mynumber);
}
}
public static int tail_factorial(int mynumber, int sofar) {
if (mynumber == 1) {
return sofar;
} else {
return tail_factorial(--mynumber, sofar * mynumber);
}
}
मेरे लिए समझने tail call recursion
का सबसे अच्छा तरीका एक विशेष मामले की पुनरावृत्ति है जहां अंतिम कॉल (या टेल कॉल) ही कार्य है।
पायथन में दिए गए उदाहरणों की तुलना:
def recsum(x):
if x == 1:
return x
else:
return x + recsum(x - 1)
^ प्रत्यावर्तन
def tailrecsum(x, running_total=0):
if x == 0:
return running_total
else:
return tailrecsum(x - 1, running_total + x)
^ जेल का दौरा
जैसा कि आप सामान्य पुनरावर्ती संस्करण में देख सकते हैं, कोड ब्लॉक में अंतिम कॉल है x + recsum(x - 1)
। इसलिए recsum
विधि को कॉल करने के बाद , एक और ऑपरेशन है जो है x + ..
।
हालांकि, पूंछ पुनरावर्ती संस्करण में, कोड ब्लॉक में अंतिम कॉल (या टेल कॉल) का tailrecsum(x - 1, running_total + x)
मतलब है कि अंतिम कॉल स्वयं विधि और उसके बाद कोई ऑपरेशन नहीं किया जाता है।
यह बिंदु महत्वपूर्ण है क्योंकि यहां देखा गया पूंछ पुनरावृत्ति स्मृति को विकसित नहीं कर रहा है क्योंकि जब अंतर्निहित वीएम एक फ़ंक्शन को पूंछ की स्थिति में खुद को बुलाता हुआ देखता है (एक फ़ंक्शन में अंतिम अभिव्यक्ति का मूल्यांकन किया जाता है), तो यह वर्तमान स्टैक फ्रेम को समाप्त करता है, जो टेल कॉल ऑप्टिमाइज़ेशन (TCO) के रूप में जाना जाता है।
एनबी। ध्यान रखें कि उपरोक्त उदाहरण पायथन में लिखा गया है जिसका रनटाइम TCO का समर्थन नहीं करता है। यह बात समझाने के लिए सिर्फ एक उदाहरण है। TCO को स्कीम, हास्केल आदि भाषाओं में सपोर्ट किया जाता है
जावा में, यहां फाइबोनैचि फ़ंक्शन का एक संभावित पूंछ पुनरावर्ती कार्यान्वयन है:
public int tailRecursive(final int n) {
if (n <= 2)
return 1;
return tailRecursiveAux(n, 1, 1);
}
private int tailRecursiveAux(int n, int iter, int acc) {
if (iter == n)
return acc;
return tailRecursiveAux(n, ++iter, acc + iter);
}
मानक पुनरावर्ती कार्यान्वयन के साथ इसका विरोध करें:
public int recursive(final int n) {
if (n <= 2)
return 1;
return recursive(n - 1) + recursive(n - 2);
}
iter
लिए घट acc
जाती है iter < (n-1)
।
मैं एक लिस्प प्रोग्रामर नहीं हूं, लेकिन मुझे लगता है कि इससे मदद मिलेगी।
मूल रूप से यह प्रोग्रामिंग की एक शैली है जैसे कि पुनरावर्ती कॉल आपके द्वारा किया जाने वाला अंतिम कार्य है।
यहां एक आम लिस्प उदाहरण है जो टेल-रिकर्सन का उपयोग करके फैक्टरियल करता है। स्टैक-कम प्रकृति के कारण, कोई भी व्यक्ति बड़ी तथ्यात्मक संगणना कर सकता है ...
(defun ! (n &optional (product 1))
(if (zerop n) product
(! (1- n) (* product n))))
और फिर मज़े के लिए आप कोशिश कर सकते थे (format nil "~R" (! 25))
संक्षेप में, एक पूंछ पुनरावर्ती के पास फ़ंक्शन में अंतिम विवरण के रूप में पुनरावर्ती कॉल है ताकि उसे पुनरावर्ती कॉल के लिए इंतजार न करना पड़े।
तो यह एक पूंछ पुनरावृत्ति है अर्थात एन (एक्स - 1, पी * एक्स) फ़ंक्शन का अंतिम विवरण है जहां संकलक यह पता लगाने के लिए चतुर है कि इसे फॉर-लूप (फैक्टरियल) के लिए अनुकूलित किया जा सकता है। दूसरा पैरामीटर p मध्यवर्ती उत्पाद मान को वहन करता है।
function N(x, p) {
return x == 1 ? p : N(x - 1, p * x);
}
यह उपरोक्त फैक्टरियल फ़ंक्शन लिखने का गैर-पूंछ-पुनरावर्ती तरीका है (हालांकि कुछ सी ++ संकलक वैसे भी अनुकूलन करने में सक्षम हो सकते हैं)।
function N(x) {
return x == 1 ? 1 : x * N(x - 1);
}
लेकिन यह नहीं है:
function F(x) {
if (x == 1) return 0;
if (x == 2) return 1;
return F(x - 1) + F(x - 2);
}
मैंने " अंडरस्टैंडिंग टेल रिकर्सन - विजुअल स्टूडियो सी ++ - असेंबली व्यू " शीर्षक से एक लंबी पोस्ट लिखी थी
यहाँ tailrecsum
पहले उल्लिखित फ़ंक्शन का एक पर्ल 5 संस्करण है ।
sub tail_rec_sum($;$){
my( $x,$running_total ) = (@_,0);
return $running_total unless $x;
@_ = ($x-1,$running_total+$x);
goto &tail_rec_sum; # throw away current stack frame
}
यह पूंछ पुनरावृत्ति के बारे में कंप्यूटर प्रोग्राम की संरचना और व्याख्या से एक अंश है ।
इसके विपरीत पुनरावृत्ति और पुनरावृत्ति में, हमें सावधान रहना चाहिए कि एक पुनरावर्ती प्रक्रिया की धारणा के साथ एक पुनरावर्ती प्रक्रिया की धारणा को भ्रमित न करें। जब हम किसी प्रक्रिया को पुनरावर्ती के रूप में वर्णित करते हैं, तो हम प्रक्रिया के लिए प्रक्रियात्मक परिभाषा को संदर्भित करते हैं, जो प्रक्रिया को संदर्भित करता है (या तो प्रत्यक्ष या अप्रत्यक्ष रूप से)। लेकिन जब हम किसी पैटर्न का अनुसरण करते हुए एक प्रक्रिया का वर्णन करते हैं, जैसे कि, रैखिक रूप से पुनरावर्ती, हम इस बारे में बोल रहे हैं कि प्रक्रिया कैसे विकसित होती है, प्रक्रिया के सिंटैक्स के बारे में नहीं कि प्रक्रिया कैसे लिखी जाती है। यह परेशान करने वाला लग सकता है कि हम पुनरावर्ती प्रक्रिया जैसे कि पुनरावृति-प्रक्रिया को संदर्भित करते हुए पुनरावृत्ति प्रक्रिया को उत्पन्न करते हैं। हालाँकि, प्रक्रिया वास्तव में पुनरावृत्त है: इसका राज्य पूरी तरह से इसके तीन राज्य चर द्वारा कब्जा कर लिया गया है, और इस प्रक्रिया को निष्पादित करने के लिए एक दुभाषिया को केवल तीन चर का ट्रैक रखने की आवश्यकता है।
एक कारण यह है कि प्रक्रिया और प्रक्रिया के बीच का अंतर भ्रामक हो सकता है कि आम भाषाओं के अधिकांश कार्यान्वयन (जिनमें अडा, पास्कल, और सी) इस तरह से डिज़ाइन किए गए हैं कि किसी भी पुनरावर्ती प्रक्रिया की व्याख्या से स्मृति की मात्रा बढ़ती है: प्रक्रिया कॉल की संख्या, तब भी जब वर्णित प्रक्रिया सिद्धांत रूप में, पुनरावृत्त है। एक परिणाम के रूप में, ये भाषाएं केवल विशेष प्रयोजन "लूपिंग कंस्ट्रक्शन" का सहारा लेकर पुनरावृत्ति प्रक्रियाओं का वर्णन कर सकती हैं, जैसे कि, दोहराएं, जब तक, के लिए, और जबकि। योजना का कार्यान्वयन इस दोष को साझा नहीं करता है। यह निरंतर स्थान में एक पुनरावृत्त प्रक्रिया को निष्पादित करेगा, भले ही पुनरावृत्ति प्रक्रिया एक पुनरावर्ती प्रक्रिया द्वारा वर्णित हो। इस संपत्ति के साथ एक कार्यान्वयन को पूंछ-पुनरावर्ती कहा जाता है। एक पूंछ-पुनरावर्ती कार्यान्वयन के साथ, पुनरावृत्ति को साधारण प्रक्रिया कॉल तंत्र का उपयोग करके व्यक्त किया जा सकता है, ताकि विशेष पुनरावृत्ति निर्माण केवल सिंटैक्टिक चीनी के रूप में उपयोगी हो।
पुनरावर्ती फ़ंक्शन एक फ़ंक्शन है जो स्वयं द्वारा कॉल करता है
यह प्रोग्रामर को कम से कम कोड का उपयोग करके कुशल प्रोग्राम लिखने की अनुमति देता है ।
नकारात्मक पक्ष यह है कि वे ठीक से नहीं लिखे जाने पर अनंत छोरों और अन्य अप्रत्याशित परिणामों का कारण बन सकते हैं ।
मैं Simple Recursive function और Tail Recursive function दोनों की व्याख्या करूँगा
एक साधारण पुनरावर्ती कार्य लिखने के लिए
दिए गए उदाहरण से:
public static int fact(int n){
if(n <=1)
return 1;
else
return n * fact(n-1);
}
उपरोक्त उदाहरण से
if(n <=1)
return 1;
पाश से बाहर निकलने के लिए निर्णायक कारक है
else
return n * fact(n-1);
क्या वास्तविक प्रसंस्करण किया जाना है
मुझे आसानी से समझने के लिए एक-एक करके टास्क को तोड़ना चाहिए।
आइए देखते हैं कि अगर मैं दौड़ता हूं तो आंतरिक रूप से क्या होता है fact(4)
public static int fact(4){
if(4 <=1)
return 1;
else
return 4 * fact(4-1);
}
If
लूप विफल हो जाता है else
इसलिए यह लूप में जाता है इसलिए यह वापस आ जाता है4 * fact(3)
स्टैक मेमोरी में, हमारे पास है 4 * fact(3)
उपसर्ग करना = ३
public static int fact(3){
if(3 <=1)
return 1;
else
return 3 * fact(3-1);
}
If
लूप विफल हो जाता है इसलिए यह else
लूप में जाता है
तो यह लौट आता है 3 * fact(2)
याद रखें हम `` `4 * तथ्य (3)` `कहते हैं
के लिए उत्पादन fact(3) = 3 * fact(2)
अब तक ढेर है 4 * fact(3) = 4 * 3 * fact(2)
स्टैक मेमोरी में, हमारे पास है 4 * 3 * fact(2)
प्रतिस्थापित करना = २
public static int fact(2){
if(2 <=1)
return 1;
else
return 2 * fact(2-1);
}
If
लूप विफल हो जाता है इसलिए यह else
लूप में जाता है
तो यह लौट आता है 2 * fact(1)
याद है हमने बुलाया 4 * 3 * fact(2)
के लिए उत्पादन fact(2) = 2 * fact(1)
अब तक ढेर है 4 * 3 * fact(2) = 4 * 3 * 2 * fact(1)
स्टैक मेमोरी में, हमारे पास है 4 * 3 * 2 * fact(1)
प्रतिस्थापित करना = १
public static int fact(1){
if(1 <=1)
return 1;
else
return 1 * fact(1-1);
}
If
पाश सत्य है
तो यह लौट आता है 1
याद है हमने बुलाया 4 * 3 * 2 * fact(1)
के लिए उत्पादन fact(1) = 1
अब तक ढेर है 4 * 3 * 2 * fact(1) = 4 * 3 * 2 * 1
अंत में, तथ्य (4) = 4 * 3 * 2 * 1 = 24 का परिणाम
पूंछ Recursion होगा
public static int fact(x, running_total=1) {
if (x==1) {
return running_total;
} else {
return fact(x-1, running_total*x);
}
}
public static int fact(4, running_total=1) {
if (x==1) {
return running_total;
} else {
return fact(4-1, running_total*4);
}
}
If
लूप विफल हो जाता है else
इसलिए यह लूप में जाता है इसलिए यह वापस आ जाता हैfact(3, 4)
स्टैक मेमोरी में, हमारे पास है fact(3, 4)
उपसर्ग करना = ३
public static int fact(3, running_total=4) {
if (x==1) {
return running_total;
} else {
return fact(3-1, 4*3);
}
}
If
लूप विफल हो जाता है इसलिए यह else
लूप में जाता है
तो यह लौट आता है fact(2, 12)
स्टैक मेमोरी में, हमारे पास है fact(2, 12)
प्रतिस्थापित करना = २
public static int fact(2, running_total=12) {
if (x==1) {
return running_total;
} else {
return fact(2-1, 12*2);
}
}
If
लूप विफल हो जाता है इसलिए यह else
लूप में जाता है
तो यह लौट आता है fact(1, 24)
स्टैक मेमोरी में, हमारे पास है fact(1, 24)
प्रतिस्थापित करना = १
public static int fact(1, running_total=24) {
if (x==1) {
return running_total;
} else {
return fact(1-1, 24*1);
}
}
If
पाश सत्य है
तो यह लौट आता है running_total
के लिए उत्पादन running_total = 24
अंत में, तथ्य (4,1) = 24 का परिणाम
टेल रिकर्सियन वह जीवन है जो आप अभी जी रहे हैं। आप बार-बार एक ही स्टैक फ्रेम को रीसायकल करते हैं, क्योंकि "पिछले" फ्रेम में लौटने का कोई कारण या साधन नहीं है। अतीत खत्म हो चुका है और ऐसा किया जा सकता है। आप एक फ्रेम प्राप्त करते हैं, हमेशा भविष्य में आगे बढ़ते हैं, जब तक कि आपकी प्रक्रिया अनिवार्य रूप से मर नहीं जाती।
जब आप कुछ प्रक्रियाओं को अतिरिक्त फ्रेम का उपयोग कर सकते हैं, तब भी सादृश्य टूट जाता है, लेकिन अभी भी पूंछ-पुनरावर्ती माना जाता है यदि स्टैक असीम रूप से नहीं बढ़ता है।
एक पूंछ पुनरावर्तन एक पुनरावर्ती कार्य है जहां फ़ंक्शन स्वयं को फ़ंक्शन के अंत ("पूंछ") पर बुलाता है जिसमें पुनरावर्ती कॉल की वापसी के बाद कोई गणना नहीं की जाती है। कई संकलक एक पुनरावर्ती कॉल को पुनरावर्ती या पुनरावृत्ति कॉल में बदलने का अनुकूलन करते हैं।
किसी संख्या के कंप्यूटिंग तथ्य की समस्या पर विचार करें।
एक सीधा दृष्टिकोण होगा:
factorial(n):
if n==0 then 1
else n*factorial(n-1)
मान लीजिए आप तथ्यात्मक (4) कहते हैं। पुनरावर्तन वृक्ष होगा:
factorial(4)
/ \
4 factorial(3)
/ \
3 factorial(2)
/ \
2 factorial(1)
/ \
1 factorial(0)
\
1
उपरोक्त मामले में अधिकतम पुनरावृत्ति की गहराई O (n) है।
हालांकि, निम्नलिखित उदाहरण पर विचार करें:
factAux(m,n):
if n==0 then m;
else factAux(m*n,n-1);
factTail(n):
return factAux(1,n);
तथ्य के लिए पुनरावर्तन पेड़ (4) होगा:
factTail(4)
|
factAux(1,4)
|
factAux(4,3)
|
factAux(12,2)
|
factAux(24,1)
|
factAux(24,0)
|
24
यहां भी, अधिकतम पुनरावृत्ति गहराई O (n) है, लेकिन कोई भी कॉल स्टैक में कोई अतिरिक्त चर नहीं जोड़ता है। इसलिए संकलक एक स्टैक के साथ दूर कर सकता है।
सामान्य पुनरावृत्ति की तुलना में पूंछ पुनरावृत्ति बहुत तेज है। यह तेज़ है क्योंकि ट्रैक रखने के लिए पूर्वजों के कॉल का आउटपुट स्टैक में नहीं लिखा जाएगा। लेकिन सामान्य पुनरावृत्ति में सभी पूर्वज ट्रैक को बनाए रखने के लिए स्टैक में लिखा आउटपुट कहते हैं।
एक पूंछ पुनरावर्ती कार्य एक पुनरावर्ती कार्य है जहां लौटने से पहले यह अंतिम ऑपरेशन करता है जो पुनरावर्ती फ़ंक्शन कॉल करता है। यही है, पुनरावर्ती फ़ंक्शन कॉल का वापसी मूल्य तुरंत वापस आ जाता है। उदाहरण के लिए, आपका कोड इस तरह दिखेगा:
def recursiveFunction(some_params):
# some code here
return recursiveFunction(some_args)
# no code after the return statement
कंपाइलर और दुभाषिए जो टेल कॉल ऑप्टिमाइज़ेशन या टेल कॉल एलिमिनेशन को लागू करते हैं, स्टैक ओवरफ्लो को रोकने के लिए पुनरावर्ती कोड का अनुकूलन कर सकते हैं। यदि आपका कंपाइलर या दुभाषिया टेल कॉल ऑप्टिमाइजेशन (जैसे कि CPython दुभाषिया) को लागू नहीं करता है तो आपके कोड को इस तरह लिखने का कोई अतिरिक्त लाभ नहीं है।
उदाहरण के लिए, यह पायथन में एक मानक पुनरावर्ती फैक्टरियल फ़ंक्शन है:
def factorial(number):
if number == 1:
# BASE CASE
return 1
else:
# RECURSIVE CASE
# Note that `number *` happens *after* the recursive call.
# This means that this is *not* tail call recursion.
return number * factorial(number - 1)
और यह गुटीय कार्य के एक पुनरावर्ती संस्करण है।
def factorial(number, accumulator=1):
if number == 0:
# BASE CASE
return accumulator
else:
# RECURSIVE CASE
# There's no code after the recursive call.
# This is tail call recursion:
return factorial(number - 1, number * accumulator)
print(factorial(5))
(ध्यान दें कि भले ही यह पायथन कोड हो, लेकिन CPython दुभाषिया टेल कॉल ऑप्टिमाइज़ेशन नहीं करता है, इसलिए आपके कोड को इस तरह व्यवस्थित करना कोई रनटाइम लाभ प्रदान नहीं करता है।)
आपको टेल कॉल ऑप्टिमाइज़ेशन का उपयोग करने के लिए अपने कोड को थोड़ा अधिक अपठनीय बनाना पड़ सकता है, जैसा कि तथ्यात्मक उदाहरण में दिखाया गया है। (उदाहरण के लिए, बेस केस अब थोड़ा सा अनपेक्षित है, और accumulator
पैरामीटर को वैश्विक चर के एक प्रकार के रूप में प्रभावी रूप से उपयोग किया जाता है।)
लेकिन टेल कॉल ऑप्टिमाइजेशन का लाभ यह है कि यह स्टैक ओवरफ्लो त्रुटियों को रोकता है। (मैं ध्यान देता हूँ कि आप पुनरावर्ती के बजाय पुनरावृत्त एल्गोरिथ्म का उपयोग करके यह लाभ प्राप्त कर सकते हैं।)
स्टैक ओवरफ्लो तब होता है जब कॉल स्टैक में बहुत सारे फ्रेम ऑब्जेक्ट्स को धक्का दिया जाता है। किसी फ़ंक्शन को कॉल करने पर फ़्रेम ऑब्जेक्ट को कॉल स्टैक पर धकेल दिया जाता है, और फ़ंक्शन वापस आने पर कॉल स्टैक को पॉपअप किया जाता है। फ़्रेम ऑब्जेक्ट में स्थानीय चर जैसी जानकारी होती है और फ़ंक्शन लौटने पर कोड की किस पंक्ति को वापस करना है।
यदि आपका पुनरावर्ती फ़ंक्शन बिना वापस आए बहुत अधिक पुनरावर्ती कॉल करता है, तो कॉल स्टैक इसकी फ़्रेम ऑब्जेक्ट सीमा को पार कर सकता है। (संख्या प्लेटफ़ॉर्म से भिन्न होती है; पायथन में यह डिफ़ॉल्ट रूप से 1000 फ्रेम ऑब्जेक्ट है।) यह स्टैक ओवरफ़्लो त्रुटि का कारण बनता है । (अरे, यहीं से इस वेबसाइट का नाम आता है!)
हालाँकि, यदि आपके पुनरावर्ती कार्य की अंतिम चीज़ पुनरावर्ती कॉल कर रही है और उसका रिटर्न वैल्यू लौटाती है, तो इसका कोई कारण नहीं है कि कॉल फ्रेम पर रहने के लिए वर्तमान फ़्रेम ऑब्जेक्ट को रखने की आवश्यकता है। आखिरकार, यदि पुनरावर्ती फ़ंक्शन कॉल के बाद कोई कोड नहीं है, तो वर्तमान फ़्रेम ऑब्जेक्ट के स्थानीय चर पर लटका देने का कोई कारण नहीं है। इसलिए हम कॉल फ्रेम पर रखने के बजाय तुरंत वर्तमान फ्रेम ऑब्जेक्ट से छुटकारा पा सकते हैं। इसका अंतिम परिणाम यह है कि आपका कॉल स्टैक आकार में नहीं बढ़ता है, और इस प्रकार अतिप्रवाह नहीं हो सकता है।
एक संकलक या दुभाषिया के पास टेल कॉल ऑप्टिमाइज़ेशन होना चाहिए, ताकि टेल कॉल ऑप्टिमाइज़ेशन को लागू किया जा सके। फिर भी, आपने अपने कॉल को पुन: उपयोग करने के लिए अपने पुनरावर्ती कार्य में कोड को फिर से व्यवस्थित किया हो सकता है, और यदि पठनीयता में यह संभावित कमी अनुकूलन के लायक है, तो यह आपके ऊपर है।
पूंछ-कॉल पुनरावृत्ति और गैर-पूंछ-कॉल पुनरावृत्ति के बीच कुछ मुख्य अंतरों को समझने के लिए हम इन तकनीकों के .NET कार्यान्वयन का पता लगा सकते हैं।
यहाँ C #, F #, और C ++ \ CLI में कुछ उदाहरणों के साथ एक लेख दिया गया है: C #, F #, और C ++ \ CLI में टेल रिसर्शन में एडवेंचर्स ।
C # टेल-कॉल रिकर्सन के लिए ऑप्टिमाइज़ नहीं करता है जबकि F # करता है।
सिद्धांत के अंतरों में लूप्स बनाम लैंबडा कैलकुलस शामिल है। C # को लूप्स को ध्यान में रखते हुए बनाया गया है जबकि F # को लैम्ब्डा कैलकुलस के सिद्धांतों से बनाया गया है। लैंबडा कैलकुलस के सिद्धांतों पर एक बहुत अच्छी (और मुफ्त) पुस्तक के लिए , अबेल्सन, सुस्मान और सुस्मान द्वारा कंप्यूटर प्रोग्राम की संरचना और व्याख्या देखें ।
बहुत अच्छे परिचयात्मक लेख के लिए एफ # में टेल कॉल के बारे में, एफ # में टेल कॉल का विस्तृत परिचय देखें । अंत में, यहां एक लेख है जो गैर-पूंछ पुनरावृत्ति और पूंछ-कॉल पुनरावृत्ति (एफ # में) के बीच अंतर को कवर करता है: एफ तेज में पूंछ-पुनरावृत्ति बनाम गैर-पूंछ पुनरावृत्ति ।
यदि आप C # और F # के बीच टेल-कॉल पुनरावृत्ति के कुछ डिज़ाइन अंतर के बारे में पढ़ना चाहते हैं, तो C # और F # में Generate Tail-Call Opcode देखें ।
यदि आप यह जानना चाहते हैं कि सी # कंपाइलर को टेल-कॉल ऑप्टिमाइज़ेशन करने से रोकने के लिए कौन सी स्थितियाँ जानना चाहते हैं, तो इस लेख को देखें: JIT CLR टेल-कॉल की स्थिति ।
दो प्रकार की पुनरावर्ती हैं: सिर की पुनरावृत्ति और पूंछ की पुनरावृत्ति।
में सिर प्रत्यावर्तन , एक समारोह में अपनी पुनरावर्ती कॉल करता है और फिर कुछ और गणना करता है, हो सकता है पुनरावर्ती कॉल का परिणाम का उपयोग कर, उदाहरण के लिए।
एक पूंछ पुनरावर्ती कार्य में, सभी गणना पहले होती हैं और पुनरावर्ती कॉल अंतिम चीज होती है।
इस सुपर भयानक पोस्ट से लिया गया । कृपया इसे पढ़ने पर विचार करें।
रिकर्सियन का अर्थ है एक फ़ंक्शन जो स्वयं को बुला रहा है। उदाहरण के लिए:
(define (un-ended name)
(un-ended 'me)
(print "How can I get here?"))
टेल-पुनर्संरचना का अर्थ है कि कार्य समाप्त करने वाली पुनरावृत्ति:
(define (un-ended name)
(print "hello")
(un-ended 'me))
देखें, अंतिम कार्य अन-एंडेड फ़ंक्शन (कार्यविधि, स्कीम शब्दजाल) में ही कॉल करना है। एक और (अधिक उपयोगी) उदाहरण है:
(define (map lst op)
(define (helper done left)
(if (nil? left)
done
(helper (cons (op (car left))
done)
(cdr left))))
(reverse (helper '() lst)))
सहायक प्रक्रिया में, यदि बाईं ओर शून्य नहीं है, तो सबसे बड़ी बात यह है कि खुद को बुलाना है (कुछ को ठीक करें और कुछ को हटाएं)। यह मूल रूप से है कि आप किसी सूची को कैसे मैप करते हैं।
पूंछ-पुनरावृत्ति का एक बड़ा फायदा है कि दुभाषिया (या संकलक, भाषा और विक्रेता पर निर्भर) इसे अनुकूलित कर सकता है, और इसे कुछ समय के लूप के बराबर में बदल सकता है। तथ्य की बात के रूप में, योजना परंपरा में, अधिकांश "के लिए" और "जबकि" लूप एक पूंछ-पुनरावृत्ति तरीके से किया जाता है (जहां तक मुझे पता है और इसके लिए कोई समय नहीं है)।
इस सवाल के बहुत सारे शानदार उत्तर हैं ... लेकिन मैं मदद नहीं कर सकता, लेकिन एक विकल्प के साथ झंकार कर सकता हूं कि "पूंछ पुनरावृत्ति" को कैसे परिभाषित किया जाए, या कम से कम "उचित पूंछ पुनरावृत्ति।" अर्थात्: एक कार्यक्रम में एक विशेष अभिव्यक्ति की संपत्ति के रूप में इसे देखना चाहिए? या किसी प्रोग्रामिंग भाषा के कार्यान्वयन के गुण के रूप में इसे देखना चाहिए ?
बाद के दृश्य के बारे में अधिक जानकारी के लिए, विल क्लिंगर, "प्रॉपर टेल रिसर्शन एंड स्पेस एफिशिएंसी" (PLDI 1998) द्वारा एक क्लासिक पेपर है, जिसने प्रोग्रामिंग भाषा कार्यान्वयन की संपत्ति के रूप में "उचित टेल रिकर्सन" को परिभाषित किया है। परिभाषा का निर्माण कार्यान्वयन विवरणों को अनदेखा करने की अनुमति देने के लिए किया गया है (जैसे कि कॉल स्टैक वास्तव में रनटाइम स्टैक के माध्यम से या फ़्रेम के ढेर-आवंटित लिंक सूची के माध्यम से दर्शाया गया है)।
इसे पूरा करने के लिए, यह स्पर्शोन्मुख विश्लेषण का उपयोग करता है: प्रोग्राम निष्पादन समय का नहीं, जैसा कि आमतौर पर देखता है, बल्कि प्रोग्राम स्पेस उपयोग के बजाय । इस तरह, ढेर-आवंटित लिंक्ड सूची बनाम एक रनटाइम कॉल स्टैक का स्थान उपयोग एसिम्पोटिक रूप से समतुल्य हो रहा है; इसलिए एक को उस प्रोग्रामिंग लैंग्वेज इम्प्लीमेंटेशन डिटेल (एक डिटेल जो निश्चित रूप से काफी हद तक प्रैक्टिस में मायने रखती है) को नजरअंदाज करने के लिए मिलती है, लेकिन जब यह निर्धारित किया जाता है कि क्या "प्रॉपर्टी टेल रिकर्सिव" होने की आवश्यकता को पूरा करता है या नहीं )
कागज कई कारणों से सावधान अध्ययन के लायक है:
यह एक प्रोग्राम की टेल एक्सप्रेशंस और टेल कॉल की इंडक्टिव परिभाषा देता है । (इस तरह की एक परिभाषा, और इस तरह के कॉल क्यों महत्वपूर्ण हैं, यहां दिए गए अधिकांश अन्य उत्तरों का विषय लगता है।)
यहां वे परिभाषाएं दी गई हैं, बस पाठ का एक स्वाद प्रदान करने के लिए:
परिभाषा 1 पूंछ भाव कोर योजना में लिखा गया प्रोग्राम के रूप में परिभाषित कर रहे हैं उपपादन द्वारा इस प्रकार है।
- लंबोदर अभिव्यक्ति का शरीर एक पूंछ अभिव्यक्ति है
- यदि
(if E0 E1 E2)
एक पूंछ अभिव्यक्ति है, तो दोनोंE1
औरE2
पूंछ अभिव्यक्तियां हैं।- और कुछ नहीं एक पूंछ अभिव्यक्ति है।
परिभाषा 2 एक टेल कॉल एक पूंछ अभिव्यक्ति है जो एक प्रक्रिया कॉल है।
(एक पूंछ पुनरावर्ती कॉल, या जैसा कि कागज़ कहता है, "सेल्फ-टेल कॉल" एक टेल कॉल का एक विशेष मामला है, जहाँ प्रक्रिया को स्वयं लागू किया जाता है।)
यह कोर योजना है, जहां प्रत्येक मशीन एक ही नमूदार व्यवहार है मूल्यांकन के लिए छह अलग "मशीनों" के लिए औपचारिक परिभाषाओं प्रदान करता है को छोड़कर के लिए asymptotic अंतरिक्ष जटिलता वर्ग है कि प्रत्येक में है।
उदाहरण के लिए, क्रमशः मशीनों के लिए परिभाषा देने के बाद, 1. स्टैक-आधारित मेमोरी प्रबंधन, 2. कचरा संग्रह लेकिन कोई पूंछ कॉल, 3. कचरा संग्रह और पूंछ कॉल, कागज आगे भी उन्नत भंडारण प्रबंधन रणनीतियों के साथ जारी है, जैसे कि 4. "evlis पूंछ प्रत्यावर्तन", जहां वातावरण एक पूंछ कॉल में पिछले उप अभिव्यक्ति तर्क के मूल्यांकन पर संरक्षित किया जाना है, 5. करने के लिए एक बंद करने के वातावरण को कम करने की जरूरत नहीं है बस कि बंद से मुक्त चर, और 6. तथाकथित "सुरक्षित-फॉर-स्पेस" शब्दार्थ द्वारा परिभाषित एपेल और शाओ ।
यह साबित करने के लिए कि मशीनें वास्तव में छह अलग-अलग अंतरिक्ष जटिलता वर्गों से संबंधित हैं, कागज, तुलना के तहत मशीनों की प्रत्येक जोड़ी के लिए, कार्यक्रमों के ठोस उदाहरण प्रदान करता है जो एक मशीन पर एसिम्प्टोटिक स्पेस ब्लूप को उजागर करेगा, लेकिन दूसरे को नहीं।
(मेरे जवाब पर अब पढ़ना, मुझे यकीन नहीं है कि अगर मैं वास्तव में क्लिंजर पेपर के महत्वपूर्ण बिंदुओं पर कब्जा करने में कामयाब रहा हूं । लेकिन, अफसोस, मैं अभी इस उत्तर को विकसित करने के लिए अधिक समय नहीं दे सकता हूं।)
बहुत से लोग पहले ही यहाँ पुनरावृत्ति के बारे में बता चुके हैं। मैं कुछ फायदों के बारे में कुछ विचारों का हवाला देना चाहूंगा जो रिकर्सार्डो टेरेल की पुस्तक "कॉन्सेप्ट इन .NET, समवर्ती और समानांतर प्रोग्रामिंग के आधुनिक पैटर्न" से देता है।
“कार्यात्मक पुनरावर्तन एफपी में पुनरावृति का प्राकृतिक तरीका है क्योंकि यह राज्य के उत्परिवर्तन से बचता है। प्रत्येक पुनरावृत्ति के दौरान, एक नया मान लूप कंस्ट्रक्टर में अपडेट किया (बदले) किया जाता है। इसके अलावा, एक पुनरावर्ती कार्य की रचना की जा सकती है, जिससे आपके कार्यक्रम को अधिक मॉड्यूलर बनाया जा सकता है, साथ ही साथ समानांतर रूप से शोषण करने के अवसरों को भी पेश किया जा सकता है। "
यहाँ भी पूंछ पुनरावृत्ति के बारे में एक ही किताब से कुछ दिलचस्प नोट हैं:
टेल-कॉल पुनरावृत्ति एक ऐसी तकनीक है जो एक नियमित पुनरावर्ती कार्य को एक अनुकूलित संस्करण में परिवर्तित करती है जो बिना किसी जोखिम और दुष्प्रभावों के बड़े इनपुट को संभाल सकती है।
नोट डेटा कॉलिंग, मेमोरी उपयोग और कैश उपयोग में सुधार के लिए एक अनुकूलन के रूप में टेल कॉल का प्राथमिक कारण है। टेल कॉल करने से, कैलली कॉलर के समान स्टैक स्पेस का उपयोग करता है। इससे मेमोरी प्रेशर कम हो जाता है। यह कैश को थोड़ा सुधारता है क्योंकि बाद में कॉल करने वालों के लिए एक ही मेमोरी का पुन: उपयोग किया जाता है और नई कैश लाइन के लिए जगह बनाने के लिए पुरानी कैश लाइन को निकालने के बजाय कैश में रह सकते हैं।