2013-04-22 39 views
10

Hãy xem xét mã này (được lấy từ here và được sửa đổi để sử dụng byte thay vì dòng ký tự).Làm thế nào để sử dụng IO với Scalaz7 Iteratees mà không làm tràn ngăn xếp?

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

Chạy mã này trên tệp có kích thước phong nha (8kb) tạo ra StackOverflowException. Một số tìm kiếm bật lên rằng các ngoại lệ có thể tránh được bằng cách sử dụng các Trampoline monad thay vì IO, nhưng điều đó không có vẻ giống như một giải pháp tuyệt vời - hy sinh chức năng tinh khiết để có được chương trình để hoàn thành ở tất cả. Cách rõ ràng để sửa lỗi này là sử dụng IO hoặc Trampoline như một biến thể Monad để bọc khác, nhưng tôi không thể tìm thấy một thực hiện của phiên bản biến áp của một trong số họ và tôi không đủ của một guru chức năng lập trình để biết làm thế nào để viết của riêng tôi (tìm hiểu thêm về FP là một trong những mục đích của dự án này, nhưng tôi nghi ngờ việc tạo ra các biến thế đơn nguyên mới cao hơn một chút so với mức hiện tại của tôi). Tôi cho rằng tôi chỉ có thể quấn một hành động IO lớn xung quanh việc tạo, chạy và trả về kết quả của các iterate của tôi, nhưng điều đó cảm thấy giống như một giải pháp thay thế một giải pháp.

Có lẽ một số monads không thể chuyển đổi thành máy biến áp đơn, vì vậy tôi muốn biết nếu nó có thể làm việc với các tệp lớn mà không làm giảm IO hoặc tràn ngăn xếp, và nếu như vậy, làm thế nào?

Câu hỏi bổ sung: Tôi không thể nghĩ ra bất kỳ cách nào để một iteratee báo hiệu rằng nó gặp phải lỗi trong khi xử lý ngoại trừ việc trả về một trong hai điều này, điều này khiến việc tạo chúng trở nên dễ dàng hơn. Đoạn mã trên cho thấy cách sử dụng EitherT để xử lý các lỗi trong điều tra viên, nhưng nó hoạt động như thế nào cho các iterate?

+0

Điều này có thể hữu ích cho bạn: http://termsandtruthconditions.herokuapp.com/blog/2013/03/16/free-monad/ – Impredicative

+0

Đó là một giải thích tốt về lý do tại sao tôi cần sử dụng tấm bạt lò xo để tránh làm tràn ngăn xếp, nhưng nó không bao gồm cách sử dụng cả IO và Trampoline. – Redattack34

+0

IO bị trampolined rồi. – Apocalisp

Trả lời

3

Sau khi tạo ngoại lệ và in chiều dài ngăn xếp ở nhiều vị trí khác nhau trong mã của bạn, tôi cảm thấy rằng của bạn không bị tràn. Tất cả dường như chạy trong kích thước ngăn xếp không đổi. Vì vậy, tôi đã tìm kiếm những nơi khác. Cuối cùng, tôi đã sao chép việc triển khai consume và thêm một số bản in chiều sâu ngăn xếp và xác nhận rằng nó đã tràn ở đó.

Vì vậy, đây tràn:

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

Nhưng, sau đó tôi phát hiện ra rằng đây không:

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

putStrTo sử dụng foldM và bằng cách nào đó không gây ra một tràn. Vì vậy, tôi tự hỏi liệu consume có thể được triển khai theo điều khoản của foldM hay không. Tôi chỉ sao chép một vài thứ từ tiêu thụ và tinh chỉnh cho đến khi được biên dịch:

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

Và nó đã hoạt động! In một danh sách dài các int.

+0

Có vẻ như luồng 'consum1' tràn với Scalaz 7.0.3, ít nhất là đối với tôi. Bạn có nhận được kết quả tương tự nếu bạn tăng kích thước luồng không? Tôi đang cố gắng theo dõi [một lỗi có thể có liên quan] (https: // github.com/scalaz/scalaz/issues/554) - Tôi nhận thấy rằng tôi bị tràn ngăn xếp nếu chạy trong ngữ cảnh 'Id', trong khi tôi gặp lỗi không gian heap nếu tôi chạy trong' Trampoline'. Tuy nhiên, với trường hợp của bạn, lỗi sẽ biến mất trong ngữ cảnh trampolined, khiến tôi nghi ngờ rằng các vấn đề có thể không liên quan sau tất cả ... –

+0

@AaronNovstrup, nó vẫn hoạt động với 100000 và scalaz 7.0.3, vì vậy có thể vấn đề của bạn thực sự là khác nhau. – huynhjl

+0

Lạ. Tôi nhìn thấy một tràn ngăn xếp với 'tiêu thụ1' trong bảng điều khiển Scala ngay cả đối với một số lượng tương đối nhỏ của các yếu tố (100), bằng cách sử dụng Scala 2.10.2, Scalaz 7.0.3, OpenJDK 64-bit máy chủ VM 1.7.0_25, và một kích thước ngăn xếp 256k. –