Usando o Scalaz Stream para analisar a tarefa (substituindo os Iteratees do Scalaz)

Introdução

eu usoScalaz 7itera em vários projetos, principalmente para processar arquivos grandes. Eu gostaria de começar a mudar para Scalazcórregos, que são projetados para substituir o pacote iteratee (que francamente está faltando um monte de peças e é uma espécie de dor de usar).

Os fluxos são baseados emmaquinas (outra variação da idéia do iterato), que temtambém foi implementado em Haskell. Eu usei a biblioteca de máquinas Haskell um pouco, mas a relação entre máquinas e fluxos não é completamente óbvia (para mim, pelo menos), e a documentação para a biblioteca de fluxos éainda um pouco escassa.

Esta pergunta é sobre uma tarefa de análise simples que gostaria de ver implementada usando fluxos em vez de iterados. Responderei a pergunta a mim mesmo se ninguém mais me vencer, mas tenho certeza de que não sou o único que está fazendo (ou pelo menos considerando) essa transição, e como preciso trabalhar com esse exercício de qualquer maneira, eu Imaginei que poderia fazer isso em público.

Tarefa

Supostamente eu tenho um arquivo contendo frases que foram simbolizadas e marcadas com partes do discurso:

no UH
, ,
it PRP
was VBD
n't RB
monday NNP
. .

the DT
equity NN
market NN
was VBD
illiquid JJ
. .

Há um token por linha, palavras e partes da fala são separadas por um único espaço, e linhas em branco representam limites de sentença. Eu quero analisar este arquivo e retornar uma lista de frases, que podemos também representar como listas de tuplas de strings:

List((no,UH), (,,,), (it,PRP), (was,VBD), (n't,RB), (monday,NNP), (.,.))
List((the,DT), (equity,NN), (market,NN), (was,VBD), (illiquid,JJ), (.,.)

Como de costume, queremos falhar de forma elegante se acertarmos exceções inválidas de entrada ou leitura de arquivo, não queremos ter que nos preocupar em fechar os recursos manualmente, etc.

Uma solução iteratee

Primeiro para algumas coisas gerais de leitura de arquivos (que realmente deveriam fazer parte do pacote iteratee, que atualmente não fornece nada remotamente a este nível alto):

import java.io.{ BufferedReader, File, FileReader }
import scalaz._, Scalaz._, effect.IO
import iteratee.{ Iteratee => I, _ }

type ErrorOr[A] = EitherT[IO, Throwable, A]

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

def enumBuffered(r: => BufferedReader) = new EnumeratorT[String, ErrorOr] {
  lazy val reader = r
  def apply[A] = (s: StepT[String, ErrorOr, A]) => s.mapCont(k =>
    tryIO(IO(Option(reader.readLine))).flatMap {
      case None       => s.pointI
      case Some(line) => k(I.elInput(line)) >>== apply[A]
    }
  )
}

def enumFile(f: File) = new EnumeratorT[String, ErrorOr] {
  def apply[A] = (s: StepT[String, ErrorOr, A]) => tryIO(
    IO(new BufferedReader(new FileReader(f)))
  ).flatMap(reader => I.iterateeT[String, ErrorOr, A](
    EitherT(
      enumBuffered(reader).apply(s).value.run.ensuring(IO(reader.close()))
    )
  ))
}

E então o nosso leitor de frases:

def sentence: IterateeT[String, ErrorOr, List[(String, String)]] = {
  import I._

  def loop(acc: List[(String, String)])(s: Input[String]):
    IterateeT[String, ErrorOr, List[(String, String)]] = s(
    el = _.trim.split(" ") match {
      case Array(form, pos) => cont(loop(acc :+ (form, pos)))
      case Array("")        => cont(done(acc, _))
      case pieces           =>
        val throwable: Throwable = new Exception(
          "Invalid line: %s!".format(pieces.mkString(" "))
        )

        val error: ErrorOr[List[(String, String)]] = EitherT.left(
          throwable.point[IO]
        )

        IterateeT.IterateeTMonadTrans[String].liftM(error)
    },
    empty = cont(loop(acc)),
    eof = done(acc, eofInput)
  )
  cont(loop(Nil))
}

E finalmente nossa ação de análise:

val action =
  I.consume[List[(String, String)], ErrorOr, List] %=
  sentence.sequenceI &=
  enumFile(new File("example.txt"))

Podemos demonstrar que isso funciona:

scala> action.run.run.unsafePerformIO().foreach(_.foreach(println))
List((no,UH), (,,,), (it,PRP), (was,VBD), (n't,RB), (monday,NNP), (.,.))
List((the,DT), (equity,NN), (market,NN), (was,VBD), (illiquid,JJ), (.,.))

E terminamos

O que eu quero

Mais ou menos o mesmo programa implementado usando streams Scalaz em vez de iterados.

questionAnswers(1)

yourAnswerToTheQuestion