Domanda

I'm using scalaz 6.0 with scala. I'm using iteratees to read from an input stream.

Here's the simple file called simple.txt I have.

This
Is
A test

My iteratee will build up an io monad to print the lines

def printLines: IterV[String, IO[Unit]] = {
  def step(currentIO: IO[Unit])(input: Input[String]): IterV[String, IO[Unit]] = 
    input match {
      case El(x) => Cont(
          step(
              currentIO.flatMap(_ => putStrLn(x))
          )
      )
      case IterV.Empty() => Cont(step(currentIO))
      case EOF() => Done(currentIO, EOF[String])
    }
  Cont(step(io()))
}

When I use the enumeratorM

getFileLines(new File(".../simple.txt"))(printLines).flatMap(_.run).unsafePerformIO

I retrieve the correct output.

When I try to use

getLines(printLines).flatMap(_.run).unsafePerformIO

I only get "This" back to the console. getLines uses the standard input stream. I've added debug statements to the iteratee and getLines seems to send EOF() after the first line and I haven't been able to resolve it.

È stato utile?

Soluzione

This is a bug in the definition of getReaderLines. Compare the current version:

/** Enumerate the lines from a BufferedReader */
def getReaderLines(r: => BufferedReader): EnumeratorM[IO, String] =
  new EnumeratorM[IO, String] {
    def apply[A](it: IterV[String, A]) = {
      def loop(i: IterV[String, A]): IO[IterV[String, A]] = i.fold(
        done = (_,_) => io { i },
        cont = k => for {
          s <- rReadLn(r)
          a <- s.map(l => loop(k(El(l)))).getOrElse(io(i))
        } yield a
      )
      loop(it)
    }
  }

With one that works:

/** Enumerate the lines from a BufferedReader */
def getReaderLines(r: => BufferedReader): EnumeratorM[IO, String] =
  new EnumeratorM[IO, String] {
    lazy val reader = r

    def apply[A](it: IterV[String, A]) = {
      def loop(i: IterV[String, A]): IO[IterV[String, A]] = i.fold(
        done = (_,_) => io { i },
        cont = k => for {
          s <- rReadLn(reader)
          a <- s.map(l => loop(k(El(l)))).getOrElse(io(i))
        } yield a
      )
      loop(it)
    }
  }

The issue is that r is a by-name parameter, which means that given the way that getLines is defined, the current version creates a new reader wrapping standard input on every loop.

Until this gets fixed in the library (and I doubt there'll be much of a rush to get 6.0.5 out the door), the simplest fix is to write your own getLines:

val getLines: EnumeratorM[IO, String] = {
  val r = new BufferedReader(new InputStreamReader(System.in))
  getReaderLines(r)
}

This will work as expected.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top