Рассмотрите этот код (взято из here и модифицировано для использования байтов, а не строк символов).Как использовать IO с Scalaz7 Iteratees без переполнения стека?
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())
}
}
Выполнение этого кода на приличном размере файла (8 КБ) производит StackOverflowException. Некоторые поиски показали, что исключение можно избежать, используя монадию Trampoline вместо IO, но это не похоже на отличное решение - пожертвовать функциональной чистотой, чтобы программа полностью завершилась. Очевидный способ исправить это - использовать IO или Trampoline в качестве Monad Transformer для переноса другого, но я не могу найти реализацию трансформаторной версии любого из них, и мне не хватает гуру функционального программирования для знаю, как писать свои собственные (более подробная информация о FP является одной из целей этого проекта, но я подозреваю, что создание новых монадных трансформаторов немного выше моего уровня на данный момент). Я полагаю, что я мог бы просто включить большое действие IO вокруг создания, запуска и возврата результата моих итераций, но это похоже на более обходное решение, чем решение.
Предположительно некоторые монады не могут быть преобразованы в трансформаторы монады, поэтому я хотел бы знать, можно ли работать с большими файлами без сброса ввода-вывода или переполнения стека, и если да, то как?
Бонусный вопрос: Я не могу придумать никакого способа для итерации, чтобы сигнализировать о том, что во время обработки он столкнулся с ошибкой при обработке, за исключением того, чтобы вернуть его. Или это упрощает их компоновку. В приведенном выше коде показано, как использовать EitherT для обработки ошибок в перечислителе, но как это работает для итераций?
Это может быть полезно вам: http://termsandtruthconditions.herokuapp.com/blog/2013/03/16/free-monad/ – Impredicative
Это хорошее объяснение того, почему мне нужно использовать Trampoline, чтобы избежать переполнения стека, но он не охватывает, как использовать как IO, так и Trampoline. – Redattack34
IO уже батут. – Apocalisp