इस फंक्शन-फंक्शन को कैसे याद किया जाता है?


114

इस रिट्रेसमेंट-फंक्शन को किस तंत्र द्वारा याद किया जाता है?

fib = (map fib' [0..] !!)                 
     where fib' 1 = 1                                                        
           fib' 2 = 1                                                        
           fib' n = fib (n-2) + fib (n-1)                    

और संबंधित नोट पर, यह संस्करण क्यों नहीं है?

fib n = (map fib' [0..] !! n)                                               
     where fib' 1 = 1                                                        
           fib' 2 = 1                                                        
           fib' n = fib (n-2) + fib (n-1)                    

13
थोड़ा असंबंधित, fib 0समाप्त नहीं करता है: आप शायद आधार मामलों के fib'लिए fib' 0 = 0और चाहते हैं fib' 1 = 1
हून

1
ध्यान दें कि पहले संस्करण को और अधिक संक्षिप्त बनाया जा सकता है: fibs = 1:1:zipWith (+) fibs (tail fibs)और fib = (fibs !!)
बैस्टियन

जवाबों:


95

हास्केल में मूल्यांकन तंत्र की आवश्यकता है : जब किसी मूल्य की आवश्यकता होती है, तो इसकी गणना की जाती है, और इसे फिर से पूछे जाने पर तैयार रखा जाता है। यदि हम कुछ सूची को परिभाषित करते हैं, xs=[0..]और बाद में इसके 100 वें तत्व के लिए पूछते हैं, xs!!99तो सूची में 100 वें स्थान पर "fleshed out" हो जाता है, 99अब नंबर को पकड़कर , अगली पहुंच के लिए तैयार है।

यही वह चाल है, जो "गोइंग-थ्रू-ए-लिस्ट" है, शोषण है। सामान्य रूप से दोगुनी फ़ाइबोनैचि परिभाषा में fib n = fib (n-1) + fib (n-2), फ़ंक्शन को स्वयं बुलाया जाता है, ऊपर से दो बार, विस्फोट विस्फोट का कारण बनता है। लेकिन उस चाल के साथ, हमने अंतरिम परिणामों के लिए एक सूची बनाई, और "सूची के माध्यम से" जाएं:

fib n = (xs!!(n-1)) + (xs!!(n-2)) where xs = 0:1:map fib [2..]

चाल उस सूची को बनाने का कारण है, और उस सूची को कॉल करने के बीच (कचरा संग्रह के माध्यम से) दूर नहीं जाने का कारण बनता है fib। इसे प्राप्त करने का सबसे आसान तरीका है, उस सूची को नाम देना"यदि आप इसे नाम देते हैं, तो यह रहेगा।"


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

पहले संस्करण के साथ, कंपाइलर हमारे साथ उदारता बरत रहा है, उस निरंतर सबप्रेप्रेशन ( map fib' [0..]) को बाहर निकालकर इसे एक अलग साझा करने योग्य इकाई बना रहा है, लेकिन ऐसा करना किसी भी दायित्व के तहत नहीं है। और वास्तव में ऐसे मामले हैं जहां हम नहीं चाहते कि यह हमारे लिए स्वचालित रूप से हो।

( संपादित करें :) इन फिर से लिखें पर विचार करें:

fib1 = f                     fib2 n = f n                 fib3 n = f n          
 where                        where                        where                
  f i = xs !! i                f i = xs !! i                f i = xs !! i       
  xs = map fib' [0..]          xs = map fib' [0..]          xs = map fib' [0..] 
  fib' 1 = 1                   fib' 1 = 1                   fib' 1 = 1          
  fib' 2 = 1                   fib' 2 = 1                   fib' 2 = 1          
  fib' i=fib1(i-2)+fib1(i-1)   fib' i=fib2(i-2)+fib2(i-1)   fib' i=f(i-2)+f(i-1)

तो असली कहानी नेस्टेड स्कोप परिभाषाओं के बारे में लगती है। 1 परिभाषा के साथ कोई बाहरी गुंजाइश नहीं है, और 3 बाहरी-गुंजाइश को कॉल न करने के लिए सावधान है fib3, लेकिन समान-स्तर f

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

लेकिन कुछ भी संकलक को यह पहचानने से रोकता है कि ऊपर के किसी भी संस्करण में आंतरिक परिभाषाएं वास्तव में बाहरी बंधन से स्वतंत्र हैं n, सभी के बाद लंबोदर उठाने के लिए, जिसके परिणामस्वरूप पूर्ण संस्मरण (बहुरूपता परिभाषाओं को छोड़कर) होता है। वास्तव में यह वही है जो सभी तीन संस्करणों के साथ होता है जब मोनोमोर्फिक प्रकारों के साथ घोषित किया जाता है और -ओ 2 ध्वज के साथ संकलित किया जाता है। बहुरूपी प्रकार की घोषणाओं के साथ, fib3स्थानीय साझाकरण और fib2कोई साझाकरण नहीं दिखाता है ।

अंत में, एक संकलक, और संकलक के उपयोग पर निर्भर करता है, और आप इसका परीक्षण कैसे करते हैं (जीएचसीआई में फाइल लोड करना, संकलित या नहीं, -O2 या नहीं, या स्टैंडअलोन के साथ), और क्या यह एक मोनोमोर्फ या पॉलीमॉर्फिक प्रकार का व्यवहार प्राप्त कर सकता है। पूरी तरह से परिवर्तन - चाहे वह स्थानीय (प्रति-कॉल) साझाकरण (यानी प्रत्येक कॉल पर रैखिक समय), ज्ञापन (पहली कॉल पर रैखिक समय, और उसी या छोटे तर्क के साथ बाद की कॉल पर 0 बार), या बिल्कुल साझा न हो ( घातांक समय)।

संक्षिप्त उत्तर है, यह एक संकलक बात है। :)


4
बस एक छोटे से विस्तार ठीक करने के लिए: दूसरे संस्करण किसी भी साझा करने जिसका मुख्य कारण स्थानीय समारोह नहीं मिलता है fib'हर के लिए नए सिरे से परिभाषित nहै और इस तरह fib'में fib 1fib'में fib 2है, जो भी निकलता है सूचियों अलग हैं। यहां तक ​​कि अगर आप मोनोमोर्फिक होने के प्रकार को ठीक करते हैं, तो भी यह व्यवहार प्रदर्शित करता है।
Vitus

1
whereखण्ड letअभिव्यक्ति की तरह साझा करने का परिचय देते हैं, लेकिन वे इस तरह की समस्याओं को छिपाने के लिए करते हैं। इसे थोड़ा और स्पष्ट रूप से दोहराते हुए
Vitus

1
अपने पुनर्लेखन के बारे में एक और दिलचस्प बात: यदि आप उन्हें मोनोमोर्फिक प्रकार (यानी Int -> Integer) देते हैं, तो fib2घातांक समय में चलता है, fib1और fib3दोनों रैखिक समय में चलते हैं, लेकिन fib1फिर भी याद किया जाता है - क्योंकि fib3स्थानीय परिभाषाओं के लिए हर किसी को फिर से परिभाषित किया गया है n
विट्टस

1
@ मिस्टरबी, लेकिन वास्तव में कंपाइलर से किसी तरह का आश्वासन लेना अच्छा होगा; किसी विशिष्ट इकाई की मेमोरी रेजीडेंसी पर किसी प्रकार का नियंत्रण। कभी-कभी हम साझा करना चाहते हैं, कभी-कभी हम इसे रोकना चाहते हैं। मैं कल्पना करता हूं / आशा है कि यह संभव होना चाहिए ...
को Ness

1
@ElizaBrandt से मेरा तात्पर्य यह था कि कभी-कभी हम किसी भारी चीज को पुनर्गणना करना चाहते हैं, इसलिए यह हमारे लिए स्मृति में बरकरार नहीं रहती है - यानी पुनर्गणना की लागत विशाल मेमोरी रिटेंशन की लागत से कम होती है। एक उदाहरण शक्तियुक्त निर्माण है: pwr (x:xs) = pwr xs ++ map (x:) pwr xs ; pwr [] = [[]]हम pwr xsदो बार स्वतंत्र रूप से गणना करना चाहते हैं , इसलिए इसे मक्खी पर इकट्ठा किया जा सकता है क्योंकि इसका उत्पादन और उपभोग किया जा रहा है।
विल नेस

23

मैं पूरी तरह से निश्चित नहीं हूं, लेकिन यहां एक शिक्षित अनुमान है:

संकलक मानता है कि fib nएक अलग पर अलग हो सकता है nऔर इस प्रकार सूची को हर बार पुनर्गणना करने की आवश्यकता होगी। whereबयान के अंदर के बिट्स पर निर्भर हो सकता है n, सब के बाद। यही है, इस मामले में, संख्याओं की पूरी सूची अनिवार्य रूप से एक कार्य है n

बिना संस्करण nएक बार सूची बना सकता है और इसे किसी फ़ंक्शन में लपेट सकता है। सूची पारित किए गए के मूल्य पर निर्भर नहीं कर सकतीn है और यह सत्यापित करना आसान है। सूची एक स्थिरांक है जिसे तब अनुक्रमित किया जाता है। यह निश्चित रूप से, एक स्थिरांक है जिसका आलसी मूल्यांकन किया जाता है, इसलिए आपका कार्यक्रम पूरी (अनंत) सूची को तुरंत प्राप्त करने का प्रयास नहीं करता है। चूंकि यह एक स्थिर है, इसे फ़ंक्शन कॉल में साझा किया जा सकता है।

यह सब याद है क्योंकि पुनरावर्ती कॉल को केवल एक सूची में एक मूल्य देखना है। चूंकि fibसंस्करण सूची को एक बार आलसी बनाता है, इसलिए यह अनावश्यक गणना किए बिना उत्तर प्राप्त करने के लिए पर्याप्त गणना करता है। यहां, "आलसी" का अर्थ है कि सूची में प्रत्येक प्रविष्टि एक थंक (एक अनवैलिड अभिव्यक्ति) है। जब आप करते thunk का मूल्यांकन, यह एक मूल्य हो जाता है, तो यह अगली बार जब कोई गणना दोहराने है तक पहुँचने। चूंकि सूची को कॉल के बीच साझा किया जा सकता है, पिछली सभी प्रविष्टियां पहले से ही उस समय की गणना की जाती हैं जब आपको अगले की आवश्यकता होती है।

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

दूसरा मामला क्यों काम करता है, इसके बारे में अधिक जानकारी के लिए, पुनरावर्ती रूप से परिभाषित सूची (zipWith के संदर्भ में रेशे) को समझें


क्या आपका मतलब "शायद fib' nएक अलग n" पर अलग हो सकता है ?
विल नेस

मुझे लगता है कि मैं बहुत स्पष्ट नहीं था: मेरा क्या मतलब था कि अंदर सब कुछ fib, सहित fib', हर अलग पर अलग हो सकता है n। मुझे लगता है कि मूल उदाहरण थोड़ा भ्रमित करने वाला है क्योंकि fib'यह अपने आप पर निर्भर करता है nजो दूसरे को छाया देता है n
तिकोन जेल्विस

20

सबसे पहले, gcc-7.4.2 के साथ, संकलित -O2गैर-संस्मरण संस्करण इतना बुरा नहीं है, फिबोनाची संख्याओं की आंतरिक सूची अभी भी फ़ंक्शन के लिए प्रत्येक शीर्ष-स्तरीय कॉल के लिए याद की जाती है। लेकिन यह अलग-अलग शीर्ष-स्तरीय कॉल के दौरान उचित रूप से याद नहीं किया जा सकता है। हालाँकि, अन्य संस्करण के लिए, सूची कॉल पर साझा की गई है।

यह मोनोमोर्फिज्म प्रतिबंध के कारण है।

पहला एक साधारण पैटर्न बाइंडिंग है (केवल नाम, कोई तर्क नहीं), इसलिए मोनोमोर्फिज्म प्रतिबंध से इसे एक मोनोमोर्फिक प्रकार प्राप्त करना होगा। अनुमान प्रकार है

fib :: (Num n) => Int -> n

और इस तरह की बाधा तयशुदा हो जाती है (डिफ़ॉल्ट घोषणा के अभाव में अन्यथा कहकर) Integer, प्रकार को ठीक करने के रूप में

fib :: Int -> Integer

इस प्रकार याद करने के लिए सिर्फ एक सूची (प्रकार की [Integer]) है।

दूसरा एक फ़ंक्शन तर्क के साथ परिभाषित किया गया है, इस प्रकार यह बहुरूपी रहता है, और यदि आंतरिक सूचियों को कॉल में याद किया गया था, तो प्रत्येक प्रकार के लिए एक सूची को याद रखना होगा Num। यह व्यावहारिक नहीं है।

दोनों संस्करणों को मोनोमोर्फिज्म प्रतिबंध अक्षम या समान प्रकार के हस्ताक्षर के साथ संकलित करें, और दोनों बिल्कुल समान व्यवहार प्रदर्शित करते हैं। (पुराने संकलक संस्करणों के लिए यह सच नहीं था, मुझे नहीं पता कि सबसे पहले किस संस्करण ने ऐसा किया था।)


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

1
@misterbee सिद्धांत रूप में, यह हो सकता है, लेकिन अगर कार्यक्रम fib 1000000बहुत प्रकार के कॉल करता है, तो यह एक टन मेमोरी खाती है। उस से बचने के लिए, किसी को एक हेरास्टिक की आवश्यकता होगी जो कि कैश से बाहर फेंकने के लिए सूचीबद्ध करता है जब यह बहुत बड़ा हो जाता है। और इस तरह की एक मेमोरिएशन रणनीति अन्य कार्यों या मूल्यों पर भी लागू होगी, संभवतः, इसलिए कंपाइलर को संभावित रूप से कई प्रकार की चीजों को याद करने के लिए बड़ी संख्या में चीजों से निपटना होगा। मुझे लगता है कि यह (आंशिक) बहुरूपी स्मरण को यथोचित रूप से अच्छे अनुमान के साथ लागू करना संभव होगा, लेकिन मुझे संदेह है कि यह सार्थक होगा।
डैनियल फिशर

5

हास्केल के लिए आपको मेमो फंक्शन की आवश्यकता नहीं है। केवल अनुभवजन्य प्रोग्रामिंग भाषा को उस कार्य की आवश्यकता होती है। हालांकि, हास्केल कार्यात्मक लंग और है ...

तो, यह बहुत तेज़ फाइबोनैचि एल्गोरिथ्म का उदाहरण है:

fib = zipWith (+) (0:(1:fib)) (1:fib)

zipWith मानक से कार्य करता है प्रस्तावना:

zipWith :: (a->b->c) -> [a]->[b]->[c]
zipWith op (n1:val1) (n2:val2) = (n1 + n2) : (zipWith op val1 val2)
zipWith _ _ _ = []

परीक्षा:

print $ take 100 fib

आउटपुट:

[1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903,2971215073,4807526976,7778742049,12586269025,20365011074,32951280099,53316291173,86267571272,139583862445,225851433717,365435296162,591286729879,956722026041,1548008755920,2504730781961,4052739537881,6557470319842,10610209857723,17167680177565,27777890035288,44945570212853,72723460248141,117669030460994,190392490709135,308061521170129,498454011879264,806515533049393,1304969544928657,2111485077978050,3416454622906707,5527939700884757,8944394323791464,14472334024676221,23416728348467685,37889062373143906,61305790721611591,99194853094755497,160500643816367088,259695496911122585,420196140727489673,679891637638612258,1100087778366101931,1779979416004714189,2880067194370816120,4660046610375530309,7540113804746346429,12200160415121876738,19740274219868223167,31940434634990099905,51680708854858323072,83621143489848422977,135301852344706746049,218922995834555169026,354224848179261915075,573147844013817084101]

समय बीत गया: 0.00018 सेकेंड


यह समाधान कमाल है!
लैरी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.