अनंत सूचियों के साथ तह बनाम तह व्यवहार


124

इस प्रश्न में myAny फ़ंक्शन के लिए कोड तह का उपयोग करता है। यह एक अनंत सूची को संसाधित करना बंद कर देता है जब विधेय संतुष्ट हो जाता है।

मैंने इसे फिर से इस्तेमाल करके तह बनाया:

myAny :: (a -> Bool) -> [a] -> Bool
myAny p list = foldl step False list
   where
      step acc item = p item || acc

(ध्यान दें कि स्टेप फंक्शन के तर्क सही उलटे हैं।)

हालाँकि, यह अनंत सूचियों को संसाधित करना बंद नहीं करता है।

मैंने Apocalisp के उत्तर में फ़ंक्शन के निष्पादन का पता लगाने का प्रयास किया :

myAny even [1..]
foldl step False [1..]
step (foldl step False [2..]) 1
even 1 || (foldl step False [2..])
False  || (foldl step False [2..])
foldl step False [2..]
step (foldl step False [3..]) 2
even 2 || (foldl step False [3..])
True   || (foldl step False [3..])
True

हालाँकि, यह फ़ंक्शन व्यवहार करने का तरीका नहीं है। यह कैसे गलत है?

जवाबों:


231

कैसे foldभिन्नता भ्रम का लगातार स्रोत प्रतीत होती है, इसलिए यहां एक अधिक सामान्य अवलोकन है:

[x1, x2, x3, x4 ... xn ]कुछ फ़ंक्शन fऔर बीज के साथ n मानों की एक सूची तह पर विचार करें z

foldl है:

  • बाएं सहयोगी :f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
  • पूंछ पुनरावर्ती : यह सूची के माध्यम से पुनरावृत्त करता है, बाद में मूल्य का उत्पादन करता है
  • आलसी : परिणाम की आवश्यकता होने तक कुछ भी मूल्यांकन नहीं किया जाता है
  • पीछे की ओर : foldl (flip (:)) []एक सूची को उलट देता है।

foldr है:

  • सही सहयोगी :f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
  • एक तर्क में पुनरावर्ती : प्रत्येक पुनरावृत्ति fअगले मूल्य और बाकी सूची को तह करने के परिणामस्वरूप लागू होता है।
  • आलसी : परिणाम की आवश्यकता होने तक कुछ भी मूल्यांकन नहीं किया जाता है
  • आगे : foldr (:) []एक सूची अपरिवर्तित देता है।

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

यह स्पष्ट रूप से कुशल पूंछ-पुनरावृत्ति से बहुत दूर रोना है जो अधिकांश कार्यात्मक प्रोग्रामर जानते हैं और प्यार करते हैं!

वास्तव में, भले ही foldlतकनीकी रूप से पूंछ-पुनरावर्ती हो, क्योंकि कुछ भी मूल्यांकन करने से पहले संपूर्ण परिणाम अभिव्यक्ति का निर्माण किया जाता है, foldlजिससे स्टैक ओवरफ्लो हो सकता है!

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

इसलिए हम देख सकते हैं कि foldrकभी-कभी अनंत सूचियों पर काम क्यों foldlनहीं किया जाता है: पूर्व में आलसी अनंत सूची को एक और आलसी अनंत डेटा संरचना में बदल सकता है, जबकि उत्तरार्द्ध को परिणाम के किसी भी हिस्से को उत्पन्न करने के लिए पूरी सूची का निरीक्षण करना चाहिए। दूसरी ओर, foldrएक ऐसे फ़ंक्शन के साथ , जिसे दोनों तर्कों की तुरंत आवश्यकता होती है, जैसे (+), काम करता है (या बल्कि, काम नहीं करता है) बहुत पसंद है foldl, इसका मूल्यांकन करने से पहले एक विशाल अभिव्यक्ति का निर्माण करना।

तो नोट करने के लिए दो महत्वपूर्ण बिंदु ये हैं:

  • foldr एक आलसी पुनरावर्ती डेटा संरचना को दूसरे में बदल सकता है।
  • अन्यथा, आलसी सिलवटों बड़े या अनंत सूचियों पर एक ढेर अतिप्रवाह के साथ दुर्घटनाग्रस्त हो जाएगा।

आपने देखा होगा कि ऐसा लगता है कि foldrसब कुछ foldlकर सकता है, और अधिक। यह सच है! वास्तव में, गुना लगभग बेकार है!

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

foldl' है:

  • बाएं सहयोगी :f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
  • पूंछ पुनरावर्ती : यह सूची के माध्यम से पुनरावृत्त करता है, बाद में मूल्य का उत्पादन करता है
  • सख्त : प्रत्येक फ़ंक्शन अनुप्रयोग का मूल्यांकन रास्ते में किया जाता है
  • पीछे की ओर : foldl' (flip (:)) []एक सूची को उलट देता है।

क्योंकि foldl'है सख्त , गणना करने के लिए परिणाम हास्केल होगा मूल्यांकन f के बजाय छोड़ दिया तर्क दे के प्रत्येक चरण पर, एक विशाल, unevaluated अभिव्यक्ति जमा। यह हमें हमेशा की तरह, कुशल पूंछ पुनरावृत्ति देता है जो हम चाहते हैं! दूसरे शब्दों में:

  • foldl' बड़ी सूची को कुशलता से मोड़ सकते हैं।
  • foldl' अनंत सूची में एक अनन्त लूप (एक ढेर अतिप्रवाह का कारण नहीं) में लटकाएगा।

हास्केल विकी का एक पृष्ठ है , जो इस पर चर्चा करता है।


6
मैं यहाँ आया था क्योंकि मैं उत्सुक हूँ क्यों foldrबेहतर है foldlमें हास्केल , जबकि विपरीत में सच है Erlang (जो मैंने पहले सीखा हास्केल )। चूंकि Erlang आलसी नहीं है और कार्य नहीं कर रहे हैं curried , इसलिए foldlमें Erlang की तरह बर्ताव करता है foldl'ऊपर। यह एक महान जवाब है! अच्छी नौकरी और धन्यवाद!
सियु चिंग पोंग -आसुका केंजी-

7
यह ज्यादातर एक महान व्याख्या है, लेकिन मुझे foldl"पिछड़े" और foldr"आगे" समस्याग्रस्त के रूप में वर्णन मिलता है। यह भाग में है क्योंकि यह इस बात पर flipलागू किया जा रहा है (:)कि गुना पिछड़ा क्यों है। प्राकृतिक प्रतिक्रिया है, "निश्चित रूप से यह पिछड़ा हुआ है: आप flipसूची को गति प्रदान करते हैं!" यह देखने के लिए भी अजीब है कि "पिछड़ा" कहा जाता है क्योंकि एक पूर्ण मूल्यांकन में पहली सूची तत्व पहले (अंतरतम) foldlपर लागू होता fहै। यह है foldrकि "पिछड़े भाग जाता है," fपहले अंतिम तत्व पर लागू होता है।
डेव अब्राहम

1
@DaveAbrahams: केवल foldlऔर foldrऔर सख्ती और अनुकूलन को अनदेखा करने के बीच , पहले का अर्थ "सबसे बाहरी" है, न कि "अंतरतम"। यही कारण है कि foldrअनंत सूचियों को संसाधित किया जा foldlसकता है और यह नहीं हो सकता है - दाएं गुना पहले fपहली सूची तत्व पर लागू होता है और (unevaluated) पूंछ को मोड़ने का परिणाम होता है, जबकि बाईं ओर को बाहरी सूची के मूल्यांकन के लिए पूरी सूची को पार करना होगा f
सीए मैककैन

1
मैं बस सोच रहा था कि क्या कोई उदाहरण है जहां तह को 'से अधिक पसंद किया जाएगा', क्या आपको लगता है कि कोई एक है?
kazuoua

1
@kazuoua जहां आलस्य जरूरी है, उदा last xs = foldl (\a z-> z) undefined xs
विल नेस

28
myAny even [1..]
foldl step False [1..]
foldl step (step False 1) [2..]
foldl step (step (step False 1) 2) [3..]
foldl step (step (step (step False 1) 2) 3) [4..]

आदि।

सहज रूप से, foldlहमेशा "बाहर" या "बाएं" पर होता है, इसलिए यह पहले विस्तारित हो जाता है। एड इन्फिटम।


10

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


0

मैं हास्केल को नहीं जानता, लेकिन स्कीम में, fold-rightहमेशा पहले सूची के अंतिम तत्व पर 'कार्य' करूंगा। इस प्रकार चक्रीय सूची के लिए काम नहीं करेगा (जो एक अनंत के समान है)।

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


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