2013-04-22 19 views
10

Bu kodu göz önünde bulundurun (here'dan alınmış ve satır karakterleri yerine bayt kullanmak için değiştirilmiştir). iyi ölçekli bir dosya (8kb) Bu kod çalıştırmaScalaz7 ile IO nasıl kullanılır? Yığın taşması olmadan yinelemeler var mı?

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()) 
    } 
} 

bir StackOverflowException üretir. Bazı aramalar istisnanın IO yerine Trampoline monad kullanılarak önlenebildiğini gösterdi, ancak bu büyük bir çözüm gibi görünmüyor - programın tamamlanmasını sağlamak için işlevsel saflığı feda ediyor. Bunu düzeltmenin bariz yolu, IO veya Trampoline'ı diğerini sarmak için Monad Transformer olarak kullanmaktır, ancak bunların her birinin transformatör versiyonunun bir uygulamasını bulamıyorum ve işlevsel bir programlama gurusu için yeterli değilim. Kendimi nasıl yazacağımı biliyorum (FP hakkında daha fazla bilgi edinmek, bu projenin amaçlarından biridir, fakat yeni monad transformatörleri yaratmanın şu anki seviyemin biraz üzerinde olduğundan şüpheleniyorum). Sanırım büyük bir IO eylemini, yineleyicilerimin sonucunu oluşturmak, yürütmek ve döndürmek etrafında sarabilirim, ama bu çözümden daha çok bir çözüm gibi görünüyor.

Muhtemelen bazı monad'ler monad trafolarına dönüştürülemez, dolayısıyla IO'yu düşürmeden veya yığının taşmasına gerek kalmadan büyük dosyalarla çalışmanın mümkün olup olmadığını bilmek isterim ve eğer öyleyse, nasıl olur?

Bonus soru: Bir yinelemenin herhangi bir şekilde geri dönüşü dışında işlem yapıldığında hatayla karşılaştığını, bunların oluşturulmasını daha kolay hale getirdiğini belirtmek için bir yol düşünemiyorum. Yukarıdaki kod numaralandırıcıdaki hataları işlemek için EitherT nasıl kullanıldığını gösterir, ancak yineleme için bu nasıl çalışır?

+0

Bu sizin için yararlı olabilir: http://termsandtruthconditions.herokuapp.com/blog/2013/03/16/free-monad/ – Impredicative

+0

Yığın taşmasını önlemek için neden Trampoline kullanmam gerektiğine dair iyi bir açıklama, ama hem IO hem de Trambolinin nasıl kullanılacağını kapsamıyor. – Redattack34

+0

IO zaten tramplen edilmiştir. – Apocalisp

cevap

3

İstisnalar oluşturduktan ve yığın uzunluğunu kodunuzun çeşitli yerlerine yazdıktan sonra, kodunuzun taşmadığını hissettim. Hepsi sabit yığın boyutunda çalışır gibi görünüyor. Bu yüzden başka yerler aradım. Sonunda consume'un uygulanmasını kopyaladım ve bir yığın derinlikli baskı ekledim ve orada taşmadığını doğruladım.

Yani bu taşıyor:

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

Ama, o zaman bu değil öğrendim:

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

putStrTo taşmaya sebep değildir nasılsa foldM kullanır ve. Bu yüzden consume'un foldM açısından uygulanıp uygulanamayacağını merak ediyorum. Sadece tüketilen birkaç şeyi kopyaladım ve derlenene kadar kopyaladım:

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] 
    } 
} 

Ve işe yaradı! Uzun bir mürekkep listesi basımı.

+0

Görünüşe göre, en azından benim için Scalaz 7.0.3 ile taşma yapıyor. Akış boyutunu artırırsanız aynı sonucu alır mısınız? Potansiyel olarak ilgili bir hatayı bulmaya çalışıyorum (https: // github.com/scalaz/scalaz/issues/554) - Eğer bir 'Id' bağlamında çalışırsam bir yığın uzay hatası alırken bir 'Trampoline' içinde çalışırsam bir yığın taşması fark ettim. Sizin durumunuzla birlikte, hata, trambolinli bir bağlamda gider, bu da bana sorunların tümüyle ilgili olmayabileceğinden şüphelenmemi sağladı ... –

+0

@AaronNovstrup, hala 100000 ve scalaz 7.0.3 ile çalışıyor, bu yüzden Sorununuz gerçekten farklı. – huynhjl

+0

Garip. Scala 2.10.2, Scalaz 7.0.3, OpenJDK 64-bit sunucu VM 1.7.0_25 kullanarak, göreceli olarak az sayıda eleman (100) için bile Scala konsolunda 'consume1' ile bir yığın taşımı görüyorum. 256k yığın boyutu. –

İlgili konular