2012-06-08 15 views
19

मैंने देखा है कि streams स्थिर समय संलग्न करने के अलावा, सूचियों की तरह बहुत कुछ कार्य करता प्रतीत होता है। बेशक, सूचियों में निरंतर समय जोड़ना बहुत जटिल नहीं है, और DList बिल्कुल ठीक है।हास्केल: लिस्ट बनाम स्ट्रीम

बाकी चर्चाओं के लिए मान लें कि या तो सूचियों में निरंतर समय शामिल है, या हम इसमें रुचि नहीं रखते हैं।

मेरा विचार यह है कि हास्केल सूचियों को केवल धाराओं के रूप में लागू किया जाना चाहिए। इस मामले होने के लिए नहीं के लिए, मुझे लगता है कि निम्नलिखित धारण करने के लिए की आवश्यकता होगी:

  1. मामलों में जहां सूचियों बेहतर धाराओं और
  2. ऐसे मामले धाराओं सूचियों की तुलना में बेहतर कर रहे हैं की तुलना में कर रहे हैं कर रहे हैं।

मेरा प्रश्न है: उपरोक्त दो मामलों के उदाहरण क्या हैं?

नोट: इस प्रश्न के प्रयोजन के लिए, कृपया चर्चा की गई विशेष कार्यान्वयन में आसानी से ठीक करने योग्य चूक को अनदेखा करें। मैं यहां मूल संरचनात्मक मतभेदों के लिए और अधिक देख रहा हूं।

अतिरिक्त जानकारी:

  1. बनाओ एक:

    मैं क्या मैं यहाँ पर हो रही है अगर हम [1..1000000] बारे में कहते हैं कि है, एक हास्केल संकलक (GHC कहते हैं) करता है का हिस्सा लगता है सूची या

  2. दो ऑब्जेक्ट्स के साथ ऑब्जेक्ट बनाएं: 1 और 1000000 जो पूरी तरह से सूची का वर्णन करता है।

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

या यदि यह मामला है (2), तो हमें धाराओं की आवश्यकता क्यों है?

+0

एचएम, आप क्या कहते हैं कि धाराओं में स्थिर समय संलग्न/प्रीपेड होता है? कार्यान्वयन से, ऐसा लगता है कि एन तत्वों को जोड़ने के परिणामस्वरूप 'चरण' फ़ंक्शन होगा जो ओ (एन) बीजों को 'या तो' कन्स्ट्रक्टर नेस्टेड ओ (एन) गहराई से पार करना होगा। प्रलेखन इस निरंतर समय का दावा कहीं भी नहीं करता है जिसे मैं देख सकता हूं। –

+0

@DanielWagner: पर्याप्त मेला। किसी भी मामले में, यह धाराओं को सूचियों की तरह और भी बनाता है। – Clinton

+0

दरअसल, यह उन्हें बहुत अलग बनाता है। सूचियों के साथ, विपक्ष मुक्त है, और आप पहली सूची की लंबाई के आधार पर स्नोक और concatenate के लिए भुगतान करते हैं; तुलनात्मक रूप से, आप धाराओं के पेड़ की गहराई के लिए भुगतान करते हैं, और जिन चीजों को संयोजित किया जा रहा है, उनके आकार अप्रासंगिक हैं। लेकिन वह अंतर नहीं है जो धाराओं को महत्वपूर्ण बनाता है। –

उत्तर

7

धाराओं का लाभ यह है कि वे अधिक शक्तिशाली हैं। इंटरफ़ेस:

data Stream m a = forall s . Stream (s -> m (Step s a)) s Size 

आपको कई चीजें करने देता है जो सामान्य सूचियां नहीं कर सकती हैं। उदाहरण के लिए:

  • ट्रैक आकार (उदाहरण के लिए अज्ञात, मैक्स 34, सटीक 12)
  • अगले तत्व प्राप्त करने के लिए monadic क्रियाएं निष्पादित करें। सूची आंशिक रूप से आलसी आईओ के साथ कर सकती है, लेकिन वह तकनीक त्रुटि प्रवण साबित हुई है, और आमतौर पर केवल शुरुआती, या साधारण छोटी स्क्रिप्ट के लिए उपयोग की जाती है।

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

कि तुलना करें सूची, जो इंटरफेस के लिए:

data [] a = a : [a] | [] 

यह बहुत सरल है, और कुछ है कि एक नया प्रोग्रामर करने के लिए आसानी से सिखाया जा सकता है।

सूचियों का एक अन्य लाभ यह है कि आप पैटर्न को आसानी से मिलान कर सकते हैं। उदाहरण के लिए:

getTwo (a : b : _) = Just (a,b) 
getTwo _ = Nothing 

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

दक्षता सूचियों का एक और संभावित लाभ भी है, क्योंकि ghc ने सूची संलयन पर बहुत समय बिताया है। बहुत सारे कोड में, मध्यवर्ती सूचियां कभी उत्पन्न नहीं होती हैं। धाराओं के साथ अनुकूलित करने के लिए यह बहुत कठिन हो सकता है।

तो मुझे लगता है कि स्ट्रीम के साथ सूचियों को स्वैप करना एक खराब विकल्प होगा। वर्तमान स्थिति बेहतर है, जहां आप उन्हें जरूरत पड़ने पर उन्हें ला सकते हैं, लेकिन शुरुआती उनकी जटिलता से फंस नहीं गए हैं और कुशल उपयोगकर्ताओं को पैटर्न मिलान खोना नहीं है।

संपादित करें: के बारे में [1..1000000]:

यह enumFromTo 1 1000000, जो lazily मूल्यांकन किया जाता है, और संलयन के अधीन (जो यह बहुत ही कुशल बनाता है) के बराबर है। जैसे sum [1..1000000] ऑप्टिमाइज़ेशन चालू होने के साथ कोई भी सूचियां (और निरंतर मेमोरी का उपयोग नहीं) उत्पन्न नहीं करेगा। तो मामला (2) सही है, यह स्थिति आलसी मूल्यांकन के कारण धाराओं के लिए एक लाभ नहीं है। जैसा ऊपर बताया गया है, धाराओं के सूचियों पर अन्य फायदे हैं।

+0

आप कहते हैं कि सूचियां सूची संलयन के कारण धाराओं की तुलना में अधिक कुशल हो सकती हैं। लेकिन धाराओं के साथ, सूचियां पहली जगह में उत्पन्न नहीं होती हैं! निश्चित रूप से कोई भी सूची किसी फ़्यूज्ड सूची से भी बदतर नहीं है। और यदि धाराओं के अंदर सूचियां हैं, तो क्या आप अभी भी उन्हें उसी तरह फ्यूज नहीं कर सकते? – Clinton

+1

"सूची उत्पन्न नहीं करना" क्या सूची संलयन करता है। कोड प्रभावी रूप से लूप के लिए सी में संकलित किया जाता है। यह डेनियल वाग्नेर जैसे सभी मामलों में ऐसा नहीं कर सकता है, लेकिन यह कई परिस्थितियों में काम करता है। –

+0

मैं सहमत हूं। लेकिन "सूची नहीं बनाते" स्ट्रीम से बेहतर "सूची नहीं बना रही" सूची कैसे है। आपको लगता है कि सूची संलयन धाराओं की तुलना में सूचियों को बेहतर बना सकता है, न कि धाराओं के बराबर। – Clinton

16

जब आप [1..1000000] लिखते हैं, तो वास्तव में जीएचसी क्या करता है वह ऑब्जेक्ट बनाता है जिसमें 1 और 1000000 शामिल है जो बताता है कि ब्याज की सूची कैसे बनाएं; उस वस्तु को "थंक" कहा जाता है। सूची केवल केस जांच की पूर्ति के लिए जरूरी है; उदाहरण के लिए, आप लिख सकते हैं:

printList [] = putStrLn "" 
printList (x:xs) = putStrLn (show x) >> printList xs 

main = printList [1..1000000] 

कौन इस तरह का मूल्यांकन करता है:

main 
= { definition of main } 
printList [1..1000000] 
= { list syntax sugar } 
printList (enumFromTo 1 1000000) 
= { definition of printList } 
case enumFromTo 1 1000000 of 
    [] -> putStrLn "" 
    x:xs -> putStrLn (show x) >> printList xs 
= { we have a case, so must start evaluating enumFromTo; 
    I'm going to skip a few steps here involving unfolding 
    the definition of enumFromTo and doing some pattern 
    matching } 
case 1 : enumFromTo 2 1000000 of 
    [] -> putStrLn "" 
    x:xs -> putStrLn (show x) >> printList xs 
= { now we know which pattern to choose } 
putStrLn (show 1) >> printList (enumFromTo 2 1000000) 

तो फिर तुम पाते हैं कि 1 कंसोल के लिए मुद्रित किया गया था, और हम enumFromTo 2 1000000 साथ शीर्ष के निकट से शुरुआत करते हैं, enumFromTo 1 1000000 के बजाय। आखिरकार, आपको सभी संख्याओं को मुद्रित किया जाएगा और

printList (enumFromTo 1000000 1000000) 
= { definition of printList } 
case enumFromTo 1000000 1000000 of 
    [] -> putStrLn "" 
    x:xs -> putStrLn (show x) >> printList xs 
= { skipping steps again to evaluate enumFromTo } 
case [] of 
    [] -> putStrLn "" 
    x:xs -> putStrLn (show x) >> printList xs 
= { now we know which pattern to pick } 
putStrLn "" 

का मूल्यांकन करने के लिए समय आएगा और मूल्यांकन समाप्त हो जाएगा।

कारण हमें धाराओं की आवश्यकता है थोड़ा सूक्ष्म है। मूल पेपर, Stream fusion: From lists to streams to nothing at all, शायद सबसे पूर्ण स्पष्टीकरण है।

concatMap foo . map bar . filter pred . break isSpecial 

... यह तो स्पष्ट है कि कैसे संकलक सभी मध्यवर्ती सूचियों दूर संकलित करने के लिए प्राप्त करने के लिए नहीं है: लघु संस्करण है कि आप एक लंबे पाइप लाइन है जब है। आप देख सकते हैं कि हम सूचियों के बारे में सोच सकते हैं कि "राज्य" का पुनरावृत्ति किया जा रहा है, और यह कि इनमें से प्रत्येक कार्य, सूची की खोज करने के बजाय, प्रत्येक पुनरावृत्ति पर राज्य को संशोधित करने के तरीके को बदल रहा है। Stream प्रकार यह स्पष्ट करने के लिए प्रयास करता है, और परिणाम स्ट्रीम संलयन है।यह इस प्रकार से दिखाई देता है: हम पहली धारा संस्करणों में इन सभी कार्यों कन्वर्ट:

(toList . S.concatMap foo . fromList) . 
(toList . S.map bar . fromList) . 
(toList . S.filter pred . fromList) . 
(toList . S.break isSpecial . fromList) 

तो देख सकते हैं कि हम हमेशा का सफाया कर सकते हैं fromList . toList:

toList . S.concatMap foo . S.map bar . S.filter pred . S.break . fromList 

... और फिर जादू क्योंकि श्रृंखला S.concatMap foo . S.map bar . S.filter pred . S.break होता है आंतरिक रूप से निर्माण करके और फिर तुरंत वास्तविक सूचियों को नष्ट करने के बजाय इसे एक इटेटरेटर बनाता है।

+0

मैंने 'Data.Vector.Fusion.Stream' स्रोत पर एक नज़र डाली है, और मुझे 'सूची' और 'toList' नहीं मिल रहा है । मेरी भावना यह है कि 'Data.Vector.Fusion.Stream' पहली जगह में सूचियां बनाने से बचाता है। क्या वह गलत है? – Clinton

+0

@ क्लिंटन मुझे सच में यकीन नहीं है कि मेरी पोस्ट का कौन सा हिस्सा आपको लगता है कि मैं सुझाव दे रहा हूं कि धारा संलयन सूचियों के माध्यम से चला जाता है। यह काफी विपरीत है: सूची संलयन धाराओं के माध्यम से चला जाता है। सूची संलयन अधिकार प्राप्त करना पूरी कारण धाराएं मौजूद हैं, क्योंकि मैंने अपने उत्तर में व्याख्या करने की कोशिश की थी। –

+0

टिप्पणी का वह हिस्सा जहां आपने कहा था: "स्ट्रीम प्रकार यह स्पष्ट करने की कोशिश करता है, और परिणाम स्ट्रीम फ़्यूज़न है। यहां यह कैसा दिखता है: हम पहले इन सभी कार्यों को स्ट्रीम संस्करणों में परिवर्तित करते हैं: (toList। S.concatMap foo सेसूची) ... "। लेकिन जब मैं 'Data.Vector.Fusion.Stream' के स्रोत को देखता हूं, तो मुझे ऐसा रूपांतरण नहीं मिल रहा है। – Clinton

6

संक्षिप्त उत्तर: सूचियां और धाराएं शक्ति में अतुलनीय हैं। स्ट्रीम मैनेडिक क्रियाओं की अनुमति देते हैं लेकिन सूचियों के विपरीत साझा करते समय साझाकरण को अस्वीकार करते हैं।

एक लंबा उत्तर:

1) प्रति एक जो सूचियों 2 के साथ लागू नहीं किया जा सकता के लिए @nanothief देखें) नीचे एक counterexample आसानी धाराओं

के साथ लागू नहीं किया जा सकता है जो समस्या यह है कि खिलौना सूची है उदाहरण आमतौर पर सूचियों की साझाकरण सुविधा का उपयोग नहीं करते हैं। यहां कोड है:

foo = map heavyFunction bar 
baz = take 5 foo 
quux = product foo 

सूचियों के साथ आप केवल एक बार भारी कार्य की गणना करते हैं। heavyFunction के अतिरिक्त गणना के बिना धाराओं के साथ baz और quux की गणना करने के लिए कोड को बनाए रखना मुश्किल होगा।

संबंधित मुद्दे