2013-04-22 6 views
10

इस कोड पर विचार करें (here से लिया गया है और वर्णों की बजाय बाइट्स का उपयोग करने के लिए संशोधित किया गया है)।ढेर बहने के बिना Scalaz7 Iteratees के साथ IO का उपयोग कैसे करें?

import java.io.{ File, InputStream, BufferedInputStream, FileInputStream } 
import scalaz._, Scalaz._, effect._, iteratee.{ Iteratee => I, _ } 
import std.list._ 

object IterateeIOExample { 
    type ErrorOr[+A] = EitherT[IO, Throwable, A] 

    def openStream(f: File) = IO(new BufferedInputStream(new FileInputStream(f))) 
    def readByte(s: InputStream) = IO(Some(s.read()).filter(_ != -1)) 
    def closeStream(s: InputStream) = IO(s.close()) 

    def tryIO[A, B](action: IO[B]) = I.iterateeT[A, ErrorOr, B] { 
    EitherT(action.catchLeft).map(r => I.sdone(r, I.emptyInput)) 
    } 

    def enumBuffered(r: => BufferedInputStream) = new EnumeratorT[Int, ErrorOr] { 
    lazy val reader = r 
    def apply[A] = (s: StepT[Int, ErrorOr, A]) => s.mapCont(k => 
     tryIO(readByte(reader)) flatMap { 
     case None => s.pointI 
     case Some(byte) => k(I.elInput(byte)) >>== apply[A] 
     }) 
    } 

    def enumFile(f: File) = new EnumeratorT[Int, ErrorOr] { 
    def apply[A] = (s: StepT[Int, ErrorOr, A]) => 
     tryIO(openStream(f)).flatMap(stream => I.iterateeT[Int, ErrorOr, A](
     EitherT(
      enumBuffered(stream).apply(s).value.run.ensuring(closeStream(stream))))) 
    } 

    def main(args: Array[String]) { 
    val action = (
     I.consume[Int, ErrorOr, List] &= 
     enumFile(new File(args(0)))).run.run 
    println(action.unsafePerformIO()) 
    } 
} 

एक सभ्य आकार फ़ाइल (8kb) पर इस कोड चल रहा है एक StackOverflowException पैदा करता है। कुछ खोजों ने यह निष्कर्ष निकाला कि आईओ के बजाय ट्रैम्पोलिन मोनैड का उपयोग करके अपवाद से बचा जा सकता है, लेकिन यह एक महान समाधान की तरह प्रतीत नहीं होता है - कार्यक्रम को पूरा करने के लिए कार्यात्मक शुद्धता बलिदान। इसे ठीक करने का स्पष्ट तरीका आईओ या ट्रैम्पोलिन को दूसरे को लपेटने के लिए एक मोनाड ट्रांसफॉर्मर के रूप में उपयोग करना है, लेकिन मुझे उनमें से किसी के ट्रांसफॉर्मर संस्करण का कार्यान्वयन नहीं मिल रहा है और मैं पर्याप्त कार्यात्मक-प्रोग्रामिंग गुरु नहीं हूं मुझे अपना खुद का लिखना है (एफपी के बारे में और अधिक सीखना इस परियोजना के प्रयोजनों में से एक है, लेकिन मुझे संदेह है कि नए मोनैड ट्रांसफार्मर बनाना इस समय मेरे स्तर से थोड़ा ऊपर है)। मुझे लगता है कि मैं अपने इटेटेट्स के परिणाम को चलाने, चलाने और वापस करने के आसपास एक बड़ी आईओ कार्रवाई को लपेट सकता हूं, लेकिन यह समाधान से अधिक कामकाज की तरह लगता है।

संभवतः कुछ मोनैड को मोनैड ट्रांसफार्मर में परिवर्तित नहीं किया जा सकता है, इसलिए मैं जानना चाहता हूं कि आईओ छोड़ने या ढेर बहने के बिना बड़ी फ़ाइलों के साथ काम करना संभव है, और यदि ऐसा है, तो कैसे?

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

+0

यह आपके लिए उपयोगी हो सकता है: http://termsandtruthconditions.herokuapp.com/blog/2013/03/16/free-monad/ – Impredicative

+0

यह एक अच्छा स्पष्टीकरण है कि मुझे स्टैम्प बहने से बचने के लिए ट्रैम्पोलिन का उपयोग करने की आवश्यकता क्यों है, लेकिन इसमें आईओ और ट्रैम्पोलिन दोनों का उपयोग करने का तरीका शामिल नहीं है। – Redattack34

+0

आईओ पहले से ही trampolined है। – Apocalisp

उत्तर

3

अपवाद बनाने और अपने कोड की विभिन्न जगहों पर अपनी स्टैक लंबाई प्रिंट करने के बाद, मुझे लगा कि आपके कोड बह रहा नहीं है। सभी स्थिर ढेर आकार में चलाने के लिए लगता है। तो मैंने अन्य स्थानों की तलाश की। आखिरकार मैंने consume के कार्यान्वयन की प्रतिलिपि बनाई और कुछ स्टैक गहराई प्रिंटिंग को जोड़ा और पुष्टि की कि यह वहां बह गया है।

तो यह overflows:

(I.consume[Int, Id, List] &= EnumeratorT.enumStream(Stream.fill(10000)(1))).run 

लेकिन, मैं तो पता चला कि यह नहीं करता है:

(I.putStrTo[Int](System.out) &= EnumeratorT.enumStream(Stream.fill(10000)(1))) 
    .run.unsafePerformIO() 

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

def consume1[E, F[_]:Monad, A[_]:PlusEmpty:Applicative]: IterateeT[E, F, A[E]] = { 
    I.foldM[E, F, A[E]](PlusEmpty[A].empty){ (acc: A[E], e: E) => 
    (Applicative[A].point(e) <+> acc).point[F] 
    } 
} 

और यह काम किया! इंक की एक लंबी सूची मुद्रण।

+0

ऐसा लगता है कि कम से कम मेरे लिए स्कालज़ 7.0.3 के साथ 'उपभोग 1' ओवरफ्लो लगता है। यदि आप स्ट्रीम आकार बढ़ाते हैं तो क्या आपको वही परिणाम मिलता है? मैं [संभावित रूप से संबंधित बग] को ट्रैक करने की कोशिश कर रहा हूं (https: // github।कॉम/स्कालाज़/स्कालाज़/अंक/554) - मैंने देखा कि अगर मैं 'आईडी 'संदर्भ में चलाता हूं, तो मुझे एक ढेर ओवरफ्लो मिलता है, जबकि मुझे एक ट्रैपोलिन में चलाने पर एक हीप स्पेस त्रुटि मिलती है। हालांकि, आपके मामले के साथ, त्रुटि एक trampolined संदर्भ में चला जाता है, जो मुझे संदेह करने के लिए प्रेरित करता है कि मुद्दों के बाद सभी संबंधित नहीं हो सकता है ... –

+0

@AaronNovstrup, यह अभी भी 100000 और scalaz 7.0.3 के साथ काम करता है, तो हो सकता है आपकी समस्या वास्तव में अलग है। – huynhjl

+0

अजीब। मैं स्काला कंसोल में 'consume1' के साथ एक स्टैक ओवरफ़्लो देख रहा हूं, यहां तक ​​कि अपेक्षाकृत छोटी संख्या में तत्वों (100) के लिए स्कैला 2.10.2, स्कालज़ 7.0.3, ओपनजेडीके 64-बिट सर्वर वीएम 1.7.0_25, और ए 256k का ढेर आकार। –

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