मैंने टेल-कॉल ऑप्टिमाइज़ेशन (टेल-रिकर्सन और कंटीन्यू-पासिंग स्टाइल दोनों को हैंडल करते हुए): https://github.com/baruchel/tco पर काम करते हुए एक मॉड्यूल प्रकाशित किया।
पायथन में पूंछ-पुनरावृत्ति का अनुकूलन
अक्सर यह दावा किया गया है कि पूंछ-पुनरावृत्ति कोडिंग के पायथोनिक तरीके के अनुरूप नहीं है और किसी को यह ध्यान नहीं देना चाहिए कि इसे लूप में कैसे एम्बेड किया जाए। मैं इस दृष्टिकोण से बहस नहीं करना चाहता; कभी-कभी मुझे विभिन्न कारणों से छोरों के बजाय पूंछ-पुनरावर्ती कार्यों के रूप में नए विचारों की कोशिश करना या लागू करना पसंद है, केवल प्रक्रिया पर विचार करने पर ध्यान केंद्रित करने के बजाय केवल तीन "पाइथोनिक" के बजाय मेरी स्क्रीन पर बीस छोटे कार्य करना। फ़ंक्शंस, मेरे कोड को संपादित करने के बजाय एक इंटरैक्टिव सत्र में काम करना, आदि)।
पायथन में पूंछ-पुनरावृत्ति का अनुकूलन वास्तव में काफी आसान है। हालांकि इसे असंभव या बहुत मुश्किल कहा जाता है, मुझे लगता है कि इसे सुरुचिपूर्ण, छोटे और सामान्य समाधानों के साथ प्राप्त किया जा सकता है; मुझे भी लगता है कि इन समाधानों में से अधिकांश पायथन सुविधाओं का उपयोग नहीं करते हैं, अन्यथा उन्हें करना चाहिए। पूंछ-पुनरावृत्ति अनुकूलन को लागू करने के लिए बहुत मानक छोरों के साथ काम करने वाले स्वच्छ लंबोदर भाव त्वरित, कुशल और पूरी तरह से उपयोग करने योग्य उपकरण का नेतृत्व करते हैं।
एक व्यक्तिगत सुविधा के रूप में, मैंने दो अलग-अलग तरीकों से इस तरह के अनुकूलन को लागू करने वाला एक छोटा मॉड्यूल लिखा। मैं अपने दो मुख्य कार्यों के बारे में यहां चर्चा करना चाहूंगा।
साफ तरीका: वाई कॉम्बीनेटर को संशोधित करना
Y Combinator अच्छी तरह से जाना जाता है; यह एक पुनरावर्ती तरीके से लैम्ब्डा कार्यों का उपयोग करने की अनुमति देता है, लेकिन यह स्वयं को लूप में पुनरावर्ती कॉल एम्बेड करने की अनुमति नहीं देता है। लैंबडा कैलकुलस अकेले ऐसा काम नहीं कर सकता। वाई कॉम्बिनेटर में थोड़ा बदलाव हालांकि पुनरावर्ती कॉल की रक्षा कर सकता है जिसका वास्तव में मूल्यांकन किया जा सकता है। इस प्रकार मूल्यांकन में देरी हो सकती है।
यहाँ Y संयोजन के लिए प्रसिद्ध अभिव्यक्ति है:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
बहुत मामूली बदलाव के साथ, मुझे मिल सकता है:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: lambda: y(y)(*args)))
स्वयं को कॉल करने के बजाय, फ़ंक्शन f अब उसी कॉल को करने वाले फ़ंक्शन को लौटाता है, लेकिन चूंकि यह इसे वापस करता है, इसलिए मूल्यांकन बाद में बाहर से किया जा सकता है।
मेरा कोड है:
def bet(func):
b = (lambda f: (lambda x: x(x))(lambda y:
f(lambda *args: lambda: y(y)(*args))))(func)
def wrapper(*args):
out = b(*args)
while callable(out):
out = out()
return out
return wrapper
फ़ंक्शन का उपयोग निम्न तरीके से किया जा सकता है; यहाँ गुटबाजी और फाइबोनैचि के पूंछ-पुनरावर्ती संस्करणों के साथ दो उदाहरण हैं:
>>> from recursion import *
>>> fac = bet( lambda f: lambda n, a: a if not n else f(n-1,a*n) )
>>> fac(5,1)
120
>>> fibo = bet( lambda f: lambda n,p,q: p if not n else f(n-1,q,p+q) )
>>> fibo(10,0,1)
55
जाहिर है कि पुनरावृत्ति की गहराई अब कोई मुद्दा नहीं है:
>>> bet( lambda f: lambda n: 42 if not n else f(n-1) )(50000)
42
यह निश्चित रूप से फ़ंक्शन का एकमात्र वास्तविक उद्देश्य है।
इस अनुकूलन के साथ केवल एक चीज नहीं की जा सकती है: इसका उपयोग दूसरे फ़ंक्शन के मूल्यांकन के लिए पूंछ-पुनरावर्ती फ़ंक्शन के साथ नहीं किया जा सकता है (यह इस तथ्य से आता है कि कॉल करने योग्य रिटर्न ऑब्जेक्ट्स को बिना किसी भेद के आगे पुनरावर्ती कॉल के रूप में नियंत्रित किया जाता है)। चूंकि मुझे आमतौर पर ऐसी सुविधा की आवश्यकता नहीं है, इसलिए मैं ऊपर दिए गए कोड से बहुत खुश हूं। हालाँकि, एक अधिक सामान्य मॉड्यूल प्रदान करने के लिए, मैंने इस मुद्दे के लिए कुछ समाधान खोजने के लिए थोड़ा और सोचा (अगले भाग देखें)।
इस प्रक्रिया की गति के बारे में (जो कि वास्तविक मुद्दा नहीं है), यह काफी अच्छा होता है; पूंछ-पुनरावर्ती कार्यों का मूल्यांकन सरल कोड का उपयोग करके निम्न कोड की तुलना में बहुत तेज किया जाता है:
def bet1(func):
def wrapper(*args):
out = func(lambda *x: lambda: x)(*args)
while callable(out):
out = func(lambda *x: lambda: x)(*out())
return out
return wrapper
मुझे लगता है कि एक अभिव्यक्ति का मूल्यांकन, यहां तक कि जटिल, कई सरल अभिव्यक्तियों का मूल्यांकन करने की तुलना में बहुत तेज है, जो इस दूसरे संस्करण में मामला है। मैंने इस नए फ़ंक्शन को अपने मॉड्यूल में नहीं रखा था, और मुझे ऐसी कोई भी परिस्थिति नहीं दिखी जहां इसे "आधिकारिक" एक के बजाय इस्तेमाल किया जा सके।
अपवादों के साथ निरंतरता शैली
यहां एक अधिक सामान्य कार्य है; यह अन्य कार्यों को वापस करने सहित सभी पूंछ-पुनरावर्ती कार्यों को संभालने में सक्षम है। पुनरावर्ती कॉल अपवादों के उपयोग द्वारा अन्य रिटर्न मूल्यों से पहचाने जाते हैं। यह समाधान पिछले वाले की तुलना में धीमा है; एक तेज कोड शायद कुछ विशेष मूल्यों का उपयोग करके लिखा जा सकता है क्योंकि मुख्य लूप में "झंडे" का पता लगाया जा रहा है, लेकिन मुझे विशेष मूल्यों या आंतरिक कीवर्ड का उपयोग करने का विचार पसंद नहीं है। अपवादों का उपयोग करने की कुछ मज़ेदार व्याख्या है: यदि पायथन पूंछ-पुनरावर्ती कॉल को पसंद नहीं करता है, तो एक अपवाद तब उठाया जाना चाहिए जब एक पूंछ-पुनरावर्ती कॉल होता है, और पायथोनिक तरीका अपवाद को पकड़ने के लिए होगा ताकि उसे साफ मिल सके समाधान, जो वास्तव में यहाँ क्या होता है ...
class _RecursiveCall(Exception):
def __init__(self, *args):
self.args = args
def _recursiveCallback(*args):
raise _RecursiveCall(*args)
def bet0(func):
def wrapper(*args):
while True:
try:
return func(_recursiveCallback)(*args)
except _RecursiveCall as e:
args = e.args
return wrapper
अब सभी कार्यों का उपयोग किया जा सकता है। निम्नलिखित उदाहरण में, f(n)
n के किसी भी सकारात्मक मान के लिए पहचान फ़ंक्शन का मूल्यांकन किया जाता है:
>>> f = bet0( lambda f: lambda n: (lambda x: x) if not n else f(n-1) )
>>> f(5)(42)
42
बेशक, यह तर्क दिया जा सकता है कि अपवाद का उपयोग जानबूझकर इंटरप्रेटर को पुनर्निर्देशित करने के लिए नहीं किया जाना चाहिए (एक तरह का goto
बयान या शायद एक तरह की निरंतरता गुजर शैली के रूप में), जिसे मुझे स्वीकार करना होगा। लेकिन, फिर से, मुझे try
एक लाइन के साथ एक return
स्टेटमेंट के रूप में उपयोग करने का विचार मज़ेदार लगता है : हम कुछ (सामान्य व्यवहार) वापस करने की कोशिश करते हैं, लेकिन एक पुनरावर्ती कॉल होने (अपवाद) के कारण हम ऐसा नहीं कर सकते।
प्रारंभिक उत्तर (2013-08-29)।
मैंने पूंछ पुनरावृत्ति से निपटने के लिए एक बहुत छोटा प्लगइन लिखा। आप इसे मेरे स्पष्टीकरण के साथ वहां पा सकते हैं: https://groups.google.com/forum/?hl=fr# .topic/ comp.lang.python / dIsnJ2BoBKs
यह एक लैंबडा फ़ंक्शन को एक अन्य फ़ंक्शन में पूंछ पुनरावृत्ति शैली के साथ लिखा जा सकता है, जो इसे लूप के रूप में मूल्यांकन करेगा।
इस छोटे से समारोह में सबसे दिलचस्प विशेषता, मेरी विनम्र राय में, यह है कि फ़ंक्शन कुछ गंदे प्रोग्रामिंग हैक पर भरोसा नहीं करता है, लेकिन मात्र लैंबडा कैलकुलस पर: फ़ंक्शन का व्यवहार दूसरे में तब बदल जाता है जब किसी अन्य लंबो फ़ंक्शन में डाला जाता है वाई कॉम्बिनेटर की तरह दिखता है।