आम सहमति की संपत्ति क्या पूंछ पुनरावृत्ति मॉडुलो विपक्ष को खत्म करने की अनुमति देती है?


14

मैं मूल पूंछ पुनरावृत्ति उन्मूलन के विचार से परिचित हूं , जहां फ़ंक्शन जो स्वयं को कॉल का प्रत्यक्ष परिणाम लौटाते हैं, पुनरावृत्त छोरों के रूप में फिर से लिखा जा सकता है।

foo(...):
    # ...
    return foo(...)

मैं यह भी समझता हूं कि, एक विशेष मामले के रूप में, फ़ंक्शन को अभी भी फिर से लिखा जा सकता है यदि पुनरावर्ती कॉल को कॉल में लपेटा जाता है cons

foo(...):
    # ...
    return (..., foo(...))

यह किस संपत्ति की consअनुमति देता है? consपुनरावर्ती रूप से इसे फिर से लिखने की हमारी क्षमता को नष्ट किए बिना एक पुनरावर्ती पूंछ कॉल को लपेटने के अलावा अन्य क्या कार्य कर सकते हैं?

जीसीसी (लेकिन क्लैंग नहीं) "टेल रिकर्सन मॉडुलो गुणा " के इस उदाहरण को अनुकूलित करने में सक्षम है , लेकिन यह स्पष्ट नहीं है कि तंत्र क्या इसे खोजने की अनुमति देता है या यह कैसे अपने परिवर्तनों को बनाता है।

pow(x, n):
    if n == 0: return 1
    else if n == 1: return x
    else: return x * pow(x, n-1)

1
आपके गॉडबोल्ट कंपाइलर एक्सप्लोरर लिंक में, आपके फंक्शन में if(n==0) return 0;(आपके प्रश्न में 1 रिटर्न की तरह नहीं है)। x^0 = 1, तो वह एक बग है। ऐसा नहीं है कि यह बाकी सवाल के लिए मायने रखता है, हालांकि; पुनरावृति asm उस विशेष मामले के लिए पहले जाँच करता है। लेकिन अजीब बात है, पुनरावृत्ति कार्यान्वयन एक बहुतायत का परिचय 1 * xदेता है जो स्रोत में मौजूद नहीं था, भले ही हम एक floatसंस्करण बनाते हैं । gcc.godbolt.org/z/eqwine (और gcc केवल साथ सफल होता है -ffast-math।)
पीटर कॉर्ड्स

@PeterCordes अच्छी पकड़। return 0तय किया गया है। 1 से गुणा दिलचस्प है। मुझे यकीन नहीं है कि इसका क्या बनाना है।
18pm पर मैक्सएम

मुझे लगता है कि इसे लूप में बदलने पर जीसीसी के बदलने का एक साइड-इफेक्ट है। स्पष्ट रूप से gcc के यहाँ कुछ छूटे हुए अनुकूलन हैं, उदाहरण के लिए इसे floatबिना खोए -ffast-math, भले ही यह वही मूल्य हो जो हर बार गुणा हो। (1.0f` को छोड़कर जो स्टिकिंग पॉइंट हो सकता है?)
पीटर कॉर्ड्स

जवाबों:


12

जबकि जीसीसी संभावना तदर्थ नियमों का उपयोग करता है, आप उन्हें निम्नलिखित तरीके से प्राप्त कर सकते हैं। मैं powवर्णन करने के लिए उपयोग करूँगा क्योंकि आप fooबहुत अस्पष्ट परिभाषित हैं। इसके अलावा, fooसबसे अच्छा भाषा के रूप में एकल काम चर के संबंध में अंतिम कॉल अनुकूलन का एक उदाहरण के रूप में समझा जा सकता है ओज है और में चर्चा के रूप में अवधारणाओं, तकनीक, और कंप्यूटर प्रोग्रामिंग के मॉडल । एकल-असाइनमेंट चर का उपयोग करने का लाभ यह है कि यह एक घोषित प्रोग्रामिंग प्रतिमान के भीतर शेष है। अनिवार्य रूप से, आपके पास fooएकल-असाइनमेंट चर द्वारा प्रतिनिधित्व किए गए संरचना रिटर्न के प्रत्येक क्षेत्र हो सकते हैं जो तब fooअतिरिक्त तर्कों के रूप में पारित किए जाते हैं । fooफिर एक पूंछ-पुनरावर्ती बन जाता हैvoidलौटने का कार्य। इसके लिए किसी खास चतुराई की जरूरत नहीं है।

powपहले पर लौटना , निरंतरता गुजर शैली में बदलना । powहो जाता है:

pow(x, n):
    return pow2(x, n, x => x)

pow2(x, n, k):
    if n == 0: return k(1)
    else if n == 1: return k(x)
    else: return pow2(x, n-1, y => k(x*y))

सभी कॉल अब टेल कॉल हैं। हालांकि, नियंत्रण स्टैक को जारी रखने वाले क्लोजर में कैप्चर किए गए वातावरण में ले जाया गया है।

अगला, निरंतरता को विक्षेपित करें। चूंकि केवल एक पुनरावर्ती कॉल है, इसलिए परिणामी डेटा संरचना जो कि डिफिशिएंलाइज्ड निरंतरता का प्रतिनिधित्व करती है, एक सूची है। हमें मिला:

pow(x, n):
    return pow2(x, n, Nil)

pow2(x, n, k):
    if n == 0: return applyPow(k, 1)
    else if n == 1: return applyPow(k, x)
    else: return pow2(x, n-1, Cons(x, k))

applyPow(k, acc):
    match k with:
        case Nil: return acc
        case Cons(x, k):
            return applyPow(k, x*acc)

क्या applyPow(k, acc)है एक सूची, अर्थात् मुक्त monoid, जैसे k=Cons(x, Cons(x, Cons(x, Nil)))और इसे में ले लो x*(x*(x*acc))। लेकिन चूंकि *सहयोगी है और आम तौर पर इकाई के साथ एक मोनॉइड बनाता है 1, इसलिए हम इस पर फिर से भरोसा कर सकते हैं((x*x)*x)*acc , और, सादगी 1के लिए, उत्पादन शुरू करने के लिए एक(((1*x)*x)*x)*acc । महत्वपूर्ण बात यह है कि हम वास्तव में हमारे पास होने से पहले ही आंशिक रूप से परिणाम की गणना कर सकते हैं acc। इसका मतलब है कि kएक सूची के रूप में चारों ओर से गुजरने के बजाय जो अनिवार्य रूप से कुछ अपूर्ण "वाक्यविन्यास" है जिसे हम अंत में "व्याख्या" करेंगे, हम इसे "व्याख्या" कर सकते हैं जैसे हम जाते हैं। नतीजा यह है कि हम जगह ले सकता है Nil, monoid की यूनिट के साथ 1इस मामले में, और Consmonoid के संचालन के साथ, *है, और अब k"चल उत्पाद" का प्रतिनिधित्व करता है।applyPow(k, acc)फिर बस वह बन जाता है k*accजिसे हम वापस इनलाइन कर सकते हैं pow2और उत्पादन को सरल बना सकते हैं :

pow(x, n):
    return pow2(x, n, 1)

pow2(x, n, k):
    if n == 0: return k
    else if n == 1: return k*x
    else: return pow2(x, n-1, k*x)

मूल की एक पूंछ-पुनरावर्ती, संचायक-गुजर शैली संस्करण pow

बेशक, मैं यह नहीं कह रहा हूं कि जीसीसी संकलन-समय पर यह सब तर्क देता है। मुझे नहीं पता कि GCC क्या तर्क देता है। मेरी बात बस एक बार इस तर्क को पूरा कर रही है, पैटर्न को पहचानना अपेक्षाकृत आसान है और मूल स्रोत कोड को तुरंत इस अंतिम रूप में अनुवाद करना है। हालाँकि, सीपीएस परिवर्तन और विक्षेपण परिवर्तन पूरी तरह से सामान्य और यांत्रिक हैं। वहाँ से फ्यूजन, वनों की कटाई, या सुपरकंपुलेशन तकनीकों का उपयोग पुनरीक्षित निरंतरताओं को खत्म करने के लिए किया जा सकता है। सट्टा रूपांतरणों को फेंक दिया जा सकता है यदि यह संभव नहीं है कि सभी जारी निरस्तीकरणों के आवंटन को समाप्त कर सकें। मुझे संदेह है, हालांकि, यह बहुत अधिक महंगा होगा, हर समय पूरी व्यापकता में, इसलिए अधिक तदर्थ दृष्टिकोण।

यदि आप हास्यास्पद होना चाहते हैं, तो आप कागज की जांच कर सकते हैं पुनर्चक्रण निरंतरता की जो सीपीएस और डेटा के रूप में निरंतरता का प्रतिनिधित्व भी करता है, लेकिन पूंछ-पुनरावृत्ति-मोडुलो-विपक्ष के समान कुछ और करता है। यह बताता है कि आप ट्रांसफॉर्मर द्वारा पॉइंटर-रिवर्सिंग एल्गोरिदम का उत्पादन कैसे कर सकते हैं।

सीपीएस ट्रांसफ़ॉर्मिंग और डिफेन्चुलाइज़िंग का यह पैटर्न समझ के लिए एक बहुत ही शक्तिशाली उपकरण है, और यहाँ सूचीपत्रों की एक श्रृंखला में अच्छे प्रभाव के लिए इसका उपयोग किया जाता है


जीसीसी निरंतरता-पासिंग स्टाइल के स्थान पर तकनीक का उपयोग करता है जो आप यहां दिखाते हैं, मेरा मानना ​​है, स्टेटिक सिंगल असाइनमेंट फॉर्म।
डेविसलर

@Davislor सीपीएस से संबंधित होने पर, SSA एक प्रक्रिया के नियंत्रण प्रवाह को प्रभावित नहीं करता है और न ही यह स्टैक को पुन: प्राप्त करता है (या अन्यथा डेटा संरचनाओं को प्रस्तुत करता है जिन्हें गतिशील रूप से आवंटित करने की आवश्यकता होती है)। जैसा कि एसएसए से संबंधित है, सीपीएस "बहुत अधिक है" यही कारण है कि प्रशासनिक सामान्य प्रपत्र (एएनएफ) एसएसए के करीब है। तो जीसीसी एसएसए का उपयोग करता है, लेकिन एसएसए नियंत्रण स्टैक के लिए नेतृत्व करने योग्य डेटा संरचना के रूप में देखने योग्य नहीं है।
डेरेक एल्किंस ने SE

सही। मैं जवाब दे रहा था, “मैं यह नहीं कह रहा हूं कि जीसीसी संकलन-समय पर यह सब तर्क देता है। मुझे नहीं पता कि GCC क्या तर्क देता है। " मेरा जवाब, इसी तरह, यह दिखा रहा था कि परिवर्तन सैद्धांतिक रूप से उचित है, यह कहते हुए कि यह किसी भी संकलक के उपयोग की कार्यान्वयन विधि नहीं है। (हालाँकि, जैसा कि आप जानते हैं, कई कंपाइलर ऑप्टिमाइज़ेशन के दौरान एक प्रोग्राम को CPS में बदल देते हैं।)
डेविसक्लर

8

मैं थोड़ी देर के लिए झाड़ी के चारों ओर पिटने जा रहा हूं, लेकिन एक बिंदु है।

semigroups

इसका उत्तर है, बाइनरी रिडक्शन ऑपरेशन की सहयोगी संपत्ति

यह बहुत सार है, लेकिन गुणा एक अच्छा उदाहरण है। यदि x , y और z कुछ प्राकृतिक संख्याएँ हैं (या पूर्णांक, या परिमेय संख्याएँ, या वास्तविक संख्याएँ, या जटिल संख्याएँ, या N × N matrices, या पूरी तरह से अधिक कोई चीज़), तो x × y एक ही तरह का है x और y दोनों की संख्या । हमने दो नंबरों के साथ शुरू किया, इसलिए यह एक बाइनरी ऑपरेशन है, और एक मिल गया है, इसलिए हमने एक-एक करके संख्याओं की गिनती कम कर दी, जिससे यह एक कमी ऑपरेशन बन गया। और ( x × y ) × z हमेशा x × ( y × ) के समान होता हैz ), जो सहयोगी संपत्ति है।

(यदि आप पहले से ही यह सब जानते हैं, तो आप अगले अनुभाग पर जा सकते हैं।)

कंप्यूटर विज्ञान में कुछ और चीजें जो आप अक्सर देखते हैं जो उसी तरह से काम करती हैं:

  • गुणा करने के बजाय किसी भी प्रकार की संख्याओं को जोड़ना
  • कंक्रीटिंग स्ट्रिंग्स ( "a"+"b"+"c"यह है "abc"कि क्या आप के साथ शुरू "ab"+"c"या "a"+"bc")
  • दो सूचियों को एक साथ विभाजित करना। [a]++[b]++[c]इसी तरह [a,b,c]या तो आगे से पीछे या सामने से पीछे है।
  • consसिर और पूंछ पर, यदि आप सिर को एक सिंगलटन सूची के रूप में समझते हैं। यह सिर्फ दो सूचियों को समाप्‍त करना है।
  • संघ या चौराहों का सेट लेना
  • बूलियन और, बूलियन या
  • बिटवाइज़ &, |और^
  • कार्यों की रचना: ( जी ) ∘ एक्स = ∘ ( ) एक्स = ( जी ( ( एक्स )))
  • अधिकतम और न्यूनतम
  • इसके अलावा मोडुलो पी

कुछ चीजें जो नहीं हैं:

  • घटाव, क्योंकि 1- (1-2) ≠ (1-1) -2
  • एक्सy = तन ( एक्स + y because ), क्योंकि tan (4/4 / 4) अपरिभाषित है
  • नकारात्मक संख्याओं पर गुणन, क्योंकि -1 × -1 ऋणात्मक संख्या नहीं है
  • पूर्णांकों का विभाजन, जिसमें तीनों समस्याएं हैं!
  • तार्किक नहीं, क्योंकि इसमें केवल एक ऑपरेंड है, दो नहीं
  • int print2(int x, int y) { return printf( "%d %d\n", x, y ); }, के रूप में print2( print2(x,y), z );और print2( x, print2(y,z) );विभिन्न उत्पादन किया है।

यह एक उपयोगी पर्याप्त अवधारणा है जिसे हमने नाम दिया है। एक ऑपरेशन के साथ एक सेट जिसमें ये गुण हैं, एक अर्धवृत्त है । तो, गुणन के अंतर्गत वास्तविक संख्या एक अर्धसमूह है। और आपका प्रश्न उन तरीकों में से एक है जो इस तरह के अमूर्त वास्तविक दुनिया में उपयोगी हो जाते हैं। सेमीग्रुप संचालन को आप जिस तरह से पूछ रहे हैं उसके अनुसार सभी को अनुकूलित किया जा सकता है।

घर पर यह कोशिश करो

जहां तक ​​मुझे पता है, इस तकनीक का वर्णन पहली बार 1974 में डैनियल फ्रीडमैन और डेविड वाइज के पेपर में किया गया था, "फोल्डिंग स्टाइलाइज्ड रिकर्सिएशन इन इटरेशन्स" , हालांकि उन्होंने कुछ और गुणों को ग्रहण किया, तुलना में उन्हें इसकी आवश्यकता थी।

हास्केल में यह वर्णन करने के लिए एक शानदार भाषा है, क्योंकि Semigroupइसके मानक पुस्तकालय में टाइपकास्ट है। यह एक सामान्य Semigroupऑपरेटर के संचालन को कहता है <>। चूँकि सूचियाँ और तार उदाहरण हैं Semigroup, इसलिए उनके उदाहरण उदाहरण के <>तौर पर संघटक संचालक के रूप में परिभाषित होते हैं ++। और सही आयात के साथ, के [a] <> [b]लिए एक उपनाम है [a] ++ [b], जो है[a,b]

लेकिन, संख्या के बारे में क्या? हम सिर्फ देखा कि सांख्यिक प्रकार के तहत semigroups हैं या तो इसके या गुणा! तो जो एक के लिए हो जाता <>है Double? खैर, या तो एक! हास्केल प्रकार परिभाषित करता है Product Double, where (<>) = (*)(जो हास्केल में वास्तविक परिभाषा है), और भी Sum Double, where (<>) = (+)

एक शिकन यह है कि आपने इस तथ्य का उपयोग किया है कि 1 गुणात्मक पहचान है। एक पहचान के साथ एक semigroup एक कहा जाता है monoid और हास्केल पैकेज में परिभाषित किया गया है Data.Monoid, जो एक typeclass के जेनेरिक पहचान तत्व कॉल memptySum, Productऔर प्रत्येक के पास एक पहचान तत्व (0, 1 और []क्रमशः) है, इसलिए वे उदाहरण Monoidभी हैं Semigroup। (एक सन्यासी के साथ भ्रमित होने की नहीं , इसलिए मुझे भूल जाओ मैं भी उन लोगों को लाया।)

आपके एल्गोरिथ्म को मोनोसेक का उपयोग करके अपने एल्गोरिथ्म का अनुवाद करने के लिए पर्याप्त जानकारी है:

module StylizedRec (pow) where

import Data.Monoid as DM

pow :: Monoid a => a -> Word -> a
{- Applies the monoidal operation of the type of x, whatever that is, by
 - itself n times.  This is already in Haskell as Data.Monoid.mtimes, but
 - let’s write it out as an example.
 -}
pow _ 0 = mempty -- Special case: Return the nullary product.
pow x 1 = x      -- The base case.
pow x n = x <> (pow x (n-1)) -- The recursive case.

महत्वपूर्ण रूप से, ध्यान दें कि यह पूंछ पुनरावर्तन मॉडुलो सेमिनग्रुप है: हर मामला या तो एक मूल्य है, एक पूंछ-पुनरावर्ती कॉल या दोनों का सेमीग्रुप उत्पाद है। इसके अलावा, यह उदाहरण memptyमामलों में से एक के लिए उपयोग करने के लिए हुआ था, लेकिन अगर हमें इसकी आवश्यकता नहीं थी, तो हम इसे अधिक टाइपकास्ट के साथ कर सकते थेSemigroup

आइए इस कार्यक्रम को GHCI में लोड करें और देखें कि यह कैसे काम करता है:

*StylizedRec> getProduct $ pow 2 4
16
*StylizedRec> getProduct $ pow 7 2
49

याद रखें कि हमने powएक सामान्य के लिए Monoidकिस प्रकार की घोषणा की , जिसे हमने टाइप किया a? हम निकालना GHCi पर्याप्त जानकारी दी है कि प्रकार aयहाँ है Product Integer, एक है जो instanceकी Monoidजिसका <>संचालन पूर्णांक गुणा है। इसलिए pow 2 4पुनरावृत्ति का विस्तार करता है 2<>2<>2<>2, जो है 2*2*2*2या16 । अब तक सब ठीक है।

लेकिन हमारा कार्य केवल सामान्य मोनोइड ऑपरेशन का उपयोग करता है। पहले, मैंने कहा कि Monoidकॉल का एक और उदाहरण है Sum, जिसका <>ऑपरेशन है +। क्या हम ऐसा प्रयास कर सकते हैं?

*StylizedRec> getSum $ pow 2 4
8
*StylizedRec> getSum $ pow 7 2
14

वही विस्तार अब हमें 2+2+2+2इसके बजाय देता है 2*2*2*2। गुणन को जोड़ना है क्योंकि घातांक गुणा करना है!

लेकिन मैंने हास्केल मोनॉयड का एक और उदाहरण दिया: सूचियां, जिनका संचालन संघनन है।

*StylizedRec> pow [2] 4
[2,2,2,2]
*StylizedRec> pow [7] 2
[7,7]

लेखन [2]कंपाइलर को बताता है कि यह एक सूची है, <>सूचियों पर है ++, ऐसा [2]++[2]++[2]++[2]है [2,2,2,2]

अंत में, एक एल्गोरिथम (दो, तथ्य में)

बस के xसाथ बदलने से [x], आप सामान्य एल्गोरिथ्म को परिवर्तित करते हैं जो पुनरावर्तन मोड्यूलो का उपयोग करता है जो एक सूची बनाता है। कौन सी सूची? एल्गोरिथ्म पर लागू <>होने वाले तत्वों की सूची । चूँकि हमने केवल वही सेग्रिगॉप ऑपरेशंस का उपयोग किया है जो सूचियों में भी है, परिणामी सूची मूल गणना के लिए आइसोमॉर्फिक होगी। और चूंकि मूल ऑपरेशन साहचर्य था, इसलिए हम समान रूप से पीछे से आगे या पीछे से तत्वों का समान रूप से मूल्यांकन कर सकते हैं।

यदि आपका एल्गोरिथ्म कभी बेस केस तक पहुंचता है और समाप्त हो जाता है, तो सूची गैर-रिक्त होगी। चूंकि टर्मिनल केस ने कुछ वापस किया, इसलिए यह सूची का अंतिम तत्व होगा, इसलिए इसमें कम से कम एक तत्व होगा।

आप क्रम में किसी सूची के प्रत्येक तत्व पर बाइनरी रिडक्शन ऑपरेशन कैसे लागू करते हैं? यह सही है, एक गुना। तो अगर आप स्थानापन्न कर सकते हैं [x]के लिए x, द्वारा कम करने के लिए तत्वों की सूची प्राप्त <>करने और फिर सही गुना या सूची छोड़ दिया गुना:

*StylizedRec> getProduct $ foldr1 (<>) $ pow [Product 2] 4
16
*StylizedRec> import Data.List
*StylizedRec Data.List> getProduct $ foldl1' (<>) $ pow [Product 2] 4
16

संस्करण foldr1वास्तव में मानक पुस्तकालय में मौजूद है, जैसा sconcatकि Semigroupऔर इसके mconcatलिए Monoid। यह सूची पर एक आलसी राइट फोल्ड करता है। यही है, यह करने के [Product 2,Product 2,Product 2,Product 2]लिए फैलता है 2<>(2<>(2<>(2)))

यह इस मामले में कुशल नहीं है क्योंकि आप व्यक्तिगत शर्तों के साथ कुछ भी नहीं कर सकते हैं जब तक कि आप उन सभी को उत्पन्न नहीं करते हैं। (एक बिंदु पर मैंने यहां चर्चा की थी कि दाएं सिलवटों का उपयोग कब करना है और कब बाएं परतों का उपयोग करना है, लेकिन यह बहुत दूर चला गया।)

जिस संस्करण के साथ foldl1'सख्ती से मूल्यांकन किया गया है, वह बाएं गुना है। यह कहना है, एक सख्त संचायक के साथ एक पूंछ-पुनरावर्ती कार्य। यह इसका मूल्यांकन करता है (((2)<>2)<>2)<>2, जब आवश्यक हो तुरंत और बाद में गणना की जाती है। (कम से कम, फोल्ड के भीतर कोई देरी नहीं होती है: फोल्ड की जा रही सूची यहां एक अन्य फ़ंक्शन द्वारा उत्पन्न की जाती है जो आलसी मूल्यांकन हो सकता है।) इसलिए, फोल्ड गणना करता है (4<>2)<>2, फिर तुरंत गणना करता है 8<>2, फिर।16 । यही कारण है कि हमें ऑपरेशन के लिए साहचर्य की आवश्यकता थी: हमने सिर्फ कोष्ठकों के समूह को बदल दिया!

सख्त बाएं गुना जीसीसी क्या कर रहा है के बराबर है। पिछले उदाहरण में सबसे बाईं संख्या संचायक है, इस मामले में एक चल उत्पाद। प्रत्येक चरण में, यह सूची में अगले नंबर से गुणा किया जाता है। इसे व्यक्त करने का दूसरा तरीका यह है: आप मूल्यों को गुणा करने के लिए पुनरावृत्ति करते हैं, चल रहे उत्पाद को एक संचायक में रखते हैं, और प्रत्येक पुनरावृत्ति पर, आप अगले मान द्वारा संचायक को गुणा करते हैं। यही है, यह whileभेस में एक पाश है।

इसे कभी-कभी केवल कुशल बनाया जा सकता है। संकलक स्मृति में सूची डेटा संरचना को दूर करने में सक्षम हो सकता है। सिद्धांत रूप में, यह संकलित करने के लिए पर्याप्त समय पर पर्याप्त जानकारी है कि इसे यहां करना चाहिए: [x]एक सिंगलटन है, इसलिए [x]<>xsजैसा है वैसा ही है cons x xs। फ़ंक्शन का प्रत्येक पुनरावृत्ति समान स्टैक फ़्रेम का फिर से उपयोग करने और जगह में मापदंडों को अपडेट करने में सक्षम हो सकता है।

किसी विशेष मामले में या तो दायां गुना या सख्त बायां गुना अधिक उपयुक्त हो सकता है, इसलिए पता करें कि आपको कौन सा चाहिए। कुछ चीजें भी हैं जो केवल एक सही तह ही कर सकती हैं (जैसे कि सभी इनपुट की प्रतीक्षा किए बिना इंटरैक्टिव आउटपुट उत्पन्न करें, और अनंत सूची पर काम करें)। यहाँ, हालांकि, हम एक साधारण मूल्य पर परिचालन के अनुक्रम को कम कर रहे हैं, इसलिए एक सख्त बाएं गुना वह है जो हम चाहते हैं।

इसलिए, जैसा कि आप देख सकते हैं, टेल-रिकर्सन मोड्यूलो को स्वचालित रूप से अनुकूलित करना संभव है, किसी भी सेमीग्रुप (जिसका एक उदाहरण गुणन के तहत सामान्य संख्यात्मक प्रकारों में से कोई है) या तो एक आलसी दाएं गुना या एक सख्त बाएं गुना, एक लाइन में हास्केल।

आगे का सामान्यीकरण

बाइनरी ऑपरेशन के दो तर्कों को एक ही प्रकार का होना जरूरी नहीं है, इसलिए जब तक प्रारंभिक मूल्य आपके परिणाम के समान है। (आप निश्चित रूप से उस तर्क को फ्लिप कर सकते हैं जो आप कर रहे हैं, बाएं या दाएं तरह के गुना के क्रम से मिलान करने के लिए।) इसलिए आप बार-बार एक अपडेट की गई फ़ाइल प्राप्त करने के लिए एक फ़ाइल में पैच जोड़ सकते हैं, या प्रारंभिक मूल्य के साथ शुरू कर सकते हैं। 1.0, एक फ्लोटिंग-पॉइंट परिणाम जमा करने के लिए पूर्णांकों द्वारा विभाजित करें। या सूची प्राप्त करने के लिए खाली सूची में तत्वों को प्रीपेन्ड करें।

एक अन्य प्रकार का सामान्यीकरण सिलवटों को सूचियों में नहीं बल्कि अन्य Foldableडेटा संरचनाओं पर लागू करना है। अक्सर, एक अपरिवर्तनीय रैखिक लिंक्ड सूची डेटा संरचना नहीं है जिसे आप किसी दिए गए एल्गोरिदम के लिए चाहते हैं। एक मुद्दा जो मुझे ऊपर नहीं मिला, वह यह है कि पीछे की तुलना में सूची के सामने तत्वों को जोड़ने के लिए यह बहुत अधिक कुशल है, और जब ऑपरेशन सराहनीय नहीं है, xतो बाईं ओर आवेदन करना और ऑपरेशन का अधिकार नहीं है वही। तो आपको एक अन्य संरचना का उपयोग करने की आवश्यकता होगी, जैसे कि सूचियों की एक जोड़ी या बाइनरी ट्री, एक एल्गोरिथ्म का प्रतिनिधित्व करने के लिए xजो दाईं ओर और बाईं ओर लागू हो सकती है <>

यह भी ध्यान दें कि सहयोगी संपत्ति आपको अन्य उपयोगी तरीकों से संचालन को फिर से व्यवस्थित करने की अनुमति देती है, जैसे कि विभाजन और जीत:

times :: Monoid a => a -> Word -> a
times _ 0 = mempty
times x 1 = x
times x n | even n    = y <> y
          | otherwise = x <> y <> y
  where y = times x (n `quot` 2)

या स्वचालित समानतावाद, जहां प्रत्येक थ्रेड एक मान को कम करता है जो बाद में दूसरों के साथ संयुक्त होता है।


1
हम यह परीक्षण करने के लिए एक प्रयोग कर सकते हैं कि सहानुभूति जीसीसी की इस अनुकूलन को करने की क्षमता की कुंजी है: एक pow(float x, unsigned n)संस्करण gcc.godbolt.org/z/eqwine केवल इसके साथ अनुकूलन करता है -ffast-math, (जिसका तात्पर्य है -fassociative-math। फ्लोटिंग फ़्लोटिंग पॉइंट बेशक सहयोगी नहीं है क्योंकि अलग-अलग अस्थायी हैं। = अलग गोलाई)। द्वारा प्रस्तुत एक 1.0f * xहै कि सी सार मशीन में मौजूद नहीं थे (लेकिन जो हमेशा एक समान परिणाम दे देंगे)। फिर n-1 गुणन जैसे do{res*=x;}while(--n!=1)पुनरावर्ती के समान होते हैं, इसलिए यह एक चूक अनुकूलन है।
पीटर कॉर्डेस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.