मैंने आमतौर पर सुना है कि उत्पादन कोड को आलसी I / O के उपयोग से बचना चाहिए। मेरा सवाल है, क्यों? क्या कभी आलसी I / O का उपयोग केवल बाहर ही करना ठीक है? और क्या विकल्प (जैसे enumerators) बेहतर बनाता है?
जवाबों:
आलसी IO में यह समस्या है कि आपने जो भी संसाधन अर्जित किया है, वह कुछ हद तक अप्रत्याशित है, क्योंकि यह इस बात पर निर्भर करता है कि आपका प्रोग्राम डेटा का कैसे उपभोग करता है - इसका "डिमांड पैटर्न"। एक बार जब आपका प्रोग्राम संसाधन का अंतिम संदर्भ छोड़ देता है, तो GC अंततः उस संसाधन को चलाएगा और रिलीज़ करेगा।
आलसी स्ट्रीम कार्यक्रम करने के लिए एक बहुत ही सुविधाजनक शैली है। यही कारण है कि शेल पाइप बहुत मजेदार और लोकप्रिय हैं।
हालाँकि, यदि संसाधन विवश हैं (जैसा कि उच्च-प्रदर्शन परिदृश्यों में, या उत्पादन वातावरण जो मशीन की सीमा के पैमाने की अपेक्षा करता है) जीसी पर निर्भर करता है तो सफाई की अपर्याप्त गारंटी हो सकती है।
स्केलेबिलिटी को बेहतर बनाने के लिए कभी-कभी आपको संसाधनों को उत्सुकता से जारी करना पड़ता है।
तो आलसी IO के विकल्प क्या हैं जो कि वृद्धिशील प्रसंस्करण पर हार नहीं मानते हैं (जो बदले में बहुत सारे संसाधनों का उपभोग करेंगे)? खैर, हमारे पास 2000 के दशक के अंत में ओलेगfoldl
किसलीव द्वारा शुरू की गई प्रसंस्करण, उर्फ पुनरावृत्तियां या प्रगणक हैं , और चूंकि नेटवर्किंग आधारित कई परियोजनाओं द्वारा लोकप्रिय है।
डेटा को आलसी धाराओं के रूप में संसाधित करने के बजाय, या एक विशाल बैच में, हम बजाय चंक-आधारित सख्त प्रसंस्करण पर अमूर्त करते हैं, संसाधन के अंतिम रूप से अंतिम रूप से पढ़े जाने के बाद गारंटी के साथ। यह इट्रैट-आधारित प्रोग्रामिंग का सार है, और एक जो बहुत अच्छा संसाधन बाधाओं की पेशकश करता है।
Iteratee आधारित IO का नकारात्मक पक्ष यह है कि इसमें कुछ हद तक अजीब प्रोग्रामिंग मॉडल है (लगभग घटना-आधारित प्रोग्रामिंग के अनुरूप, बनाम अच्छा धागा-आधारित नियंत्रण)। यह निश्चित रूप से एक उन्नत तकनीक है, किसी भी प्रोग्रामिंग भाषा में। और प्रोग्रामिंग की अधिकांश समस्याओं के लिए, आलसी IO पूरी तरह से संतोषजनक है। हालाँकि, यदि आप कई फाइलें खोल रहे हैं, या कई सॉकेट्स पर बात कर रहे हैं, या अन्यथा कई एक साथ संसाधनों का उपयोग करते हुए, एक iteratee (या प्रगणक) दृष्टिकोण समझ में आ सकता है।
डॉन्स ने एक बहुत अच्छा जवाब दिया है, लेकिन वह छोड़ दिया है जो (मेरे लिए) है, जो कि iteratees की सबसे सम्मोहक विशेषताओं में से एक है: वे अंतरिक्ष प्रबंधन के बारे में तर्क करना आसान बनाते हैं क्योंकि पुराने डेटा को स्पष्ट रूप से बनाए रखा जाना चाहिए। विचार करें:
average :: [Float] -> Float
average xs = sum xs / length xs
यह एक प्रसिद्ध अंतरिक्ष रिसाव है, क्योंकि पूरी सूची xs
को दोनों की गणना करने के लिए स्मृति में बनाए रखा जाना चाहिए sum
और length
। तह बनाकर एक कुशल उपभोक्ता बनाना संभव है:
average2 :: [Float] -> Float
average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs
-- N.B. this will build up thunks as written, use a strict pair and foldl'
लेकिन हर स्ट्रीम प्रोसेसर के लिए ऐसा करना कुछ असुविधाजनक है। कुछ सामान्यीकरण ( कॉनल इलियट - ब्यूटीफुल फोल्ड जिपिंग ) हैं, लेकिन वे इसे पकड़ नहीं पाते हैं। हालाँकि, iteratees आपको अभिव्यक्ति का समान स्तर प्राप्त कर सकते हैं।
aveIter = uncurry (/) <$> I.zip I.sum I.length
यह एक तह के रूप में कुशल नहीं है क्योंकि सूची अभी भी कई बार से अधिक पुनरावृत्त होती है, हालांकि यह विखंडू में एकत्र की जाती है इसलिए पुराने डेटा को कुशलतापूर्वक एकत्र किया जा सकता है। उस संपत्ति को तोड़ने के लिए, संपूर्ण इनपुट को स्पष्ट रूप से बनाए रखना आवश्यक है, जैसे कि stream2list:
badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list
प्रोग्रामिंग मॉडल के रूप में पुनरावृत्तियों की स्थिति प्रगति में एक कार्य है, हालांकि यह एक साल पहले की तुलना में बहुत बेहतर है। हम सीख रहे हैं कि कॉम्बिनेटर क्या उपयोगी हैं (उदाहरण zip
के लिए breakE
, enumWith
) और जो कि कम हैं, इस परिणाम के साथ कि अंतर्निर्मित पुनरावृत्तियां और कॉम्बिनेटर लगातार अधिक अभिव्यंजकता प्रदान करते हैं।
उस ने कहा, डॉन्स सही है कि वे एक उन्नत तकनीक हैं; मैं निश्चित रूप से हर I / O समस्या के लिए उनका उपयोग नहीं करता।
मैं हर समय उत्पादन कोड में आलसी I / O का उपयोग करता हूं। यह केवल कुछ परिस्थितियों में एक समस्या है, जैसे डॉन का उल्लेख किया गया है। लेकिन सिर्फ कुछ फ़ाइलों को पढ़ने के लिए यह ठीक काम करता है।
अद्यतन: हाल ही में हैस्केल -कैफे पर ओलेग किसेलजोव ने दिखाया कि unsafeInterleaveST
(जो कि एसटी मोनड के भीतर आलसी आईओ को लागू करने के लिए उपयोग किया जाता है) बहुत असुरक्षित है - यह समान तर्क को तोड़ता है। वह दिखाता है कि यह bad_ctx :: ((Bool,Bool) -> Bool) -> Bool
ऐसे निर्माण की अनुमति देता है
> bad_ctx (\(x,y) -> x == y)
True
> bad_ctx (\(x,y) -> y == x)
False
हालांकि ==
यह सराहनीय है।
आलसी IO के साथ एक और समस्या: वास्तविक IO ऑपरेशन को तब तक के लिए स्थगित किया जा सकता है जब तक कि फ़ाइल बंद न हो जाए, उदाहरण के लिए। हास्केल विकी से उद्धृत - आलसी IO के साथ समस्याएं :
उदाहरण के लिए, एक सामान्य शुरुआती गलती एक फ़ाइल को बंद करने से पहले होती है, ताकि कोई उसे पढ़ सके:
wrong = do fileData <- withFile "test.txt" ReadMode hGetContents putStr fileData
समस्या तब होती है जब फ़ाइल दाता मजबूर होने से पहले हैंडल बंद कर देता है। सही तरीका यह है कि सभी कोड को withFile पर पास किया जाए:
right = withFile "test.txt" ReadMode $ \handle -> do fileData <- hGetContents handle putStr fileData
यहां, डेटा का उपयोग फ़ाइनल फ़िनिश से पहले किया जाता है।
यह अक्सर अनपेक्षित और आसान करने वाली त्रुटि है।
यह भी देखें: लेज़ी आई / ओ के साथ समस्याओं के तीन उदाहरण ।
hGetContents
और withFile
व्यर्थ है क्योंकि पूर्व एक "छद्म-बंद" स्थिति में हैंडल डालता है और आपके लिए बंद हो जाएगा (आलसी) इसलिए कोड बिल्कुल समान है readFile
, या openFile
बिना भी hClose
। यह मूल रूप से आलसी I / O है । यदि आप उपयोग नहीं करते हैं readFile
, getContents
या hGetContents
आप आलसी I / O का उपयोग नहीं कर रहे हैं। उदाहरण के लिए line <- withFile "test.txt" ReadMode hGetLine
ठीक काम करता है।
hGetContents
आपके लिए फ़ाइल बंद करना संभाल लेगा, लेकिन इसे स्वयं "बंद" करने की अनुमति भी है, और यह सुनिश्चित करने में मदद करता है कि संसाधन अनुमानित रूप से जारी किए गए हैं।
आलसी IO के साथ एक और समस्या जिसका अब तक उल्लेख नहीं किया गया है, वह है आश्चर्यजनक व्यवहार। एक सामान्य हास्केल कार्यक्रम में, कभी-कभी यह अनुमान लगाना मुश्किल हो सकता है कि आपके कार्यक्रम के प्रत्येक भाग का मूल्यांकन कब किया जाए, लेकिन सौभाग्य से पवित्रता के कारण यह वास्तव में कोई फर्क नहीं पड़ता जब तक कि आपके पास प्रदर्शन की समस्या न हो। जब आलसी IO पेश किया जाता है, तो आपके कोड के मूल्यांकन क्रम का वास्तव में इसके अर्थ पर प्रभाव पड़ता है, इसलिए आपके द्वारा हानिरहित सोच के लिए उपयोग किए जाने वाले परिवर्तनों से आपको वास्तविक समस्याएं हो सकती हैं।
एक उदाहरण के रूप में, यहां कोड के बारे में एक प्रश्न है जो उचित लगता है लेकिन आस्थगित IO द्वारा अधिक भ्रमित किया जाता है: withFile बनाम ओपन
ये समस्याएं हमेशा घातक नहीं होती हैं, लेकिन यह सोचने वाली बात है, और एक पर्याप्त गंभीर सिरदर्द है कि मैं व्यक्तिगत रूप से आलसी आईओ से बचता हूं, जब तक कि सभी काम करने में कोई वास्तविक समस्या न हो।