48 votes

Utilisation de Scalaz Stream pour l'analyse de tâche (en remplacement de Scalaz Iteratees)

Introduction

J'utilise Scalaz 7'iteratees dans un certain nombre de projets, principalement pour le traitement de grandes-ish fichiers. Je voudrais commencer commutation de Scalaz ruisseaux, qui sont conçus pour remplacer l'iteratee paquet (qui, franchement, il manque beaucoup de pièces, et est une sorte de douleur à l'utilisation).

Les flux sont basés sur les machines (une autre variation sur le iteratee idée), qui ont également été mis en place en Haskell. J'ai utilisé le Haskell machines bibliothèque un peu, mais la relation entre les machines et les ruisseaux n'est pas tout à fait évident (pour moi, au moins), et la documentation pour les ruisseaux de la bibliothèque est encore un peu clairsemé.

Cette question est à propos d'une simple analyse de la tâche que j'aimerais voir mis en œuvre à l'aide de flux au lieu de iteratees. Je vais répondre à la question moi-même si personne ne me bat, mais je suis sûr que je ne suis pas le seul à faire (ou à tout le moins examiner) cette transition, et comme j'ai besoin de travailler à travers cet exercice, de toute façon, j'ai pensé que je pourrais aussi bien le faire en public.

Tâche

Censés j'ai un fichier contenant des phrases qui ont été segmentées et marqué avec les parties du discours:

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

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

Il y a un jeton par ligne, des mots et des parties du discours sont séparés par un seul espace, et les lignes vides représentent les limites de la phrase. Je veux analyser ce fichier et retourne une liste de phrases, que l'on pourrait aussi bien représenter que des listes de n-uplets de chaînes de caractères:

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

Comme d'habitude, nous voulons à l'échec gracieusement, si l'on touche entrée non valide ou de la lecture du fichier d'exceptions, nous ne voulons pas avoir à s'inquiéter à propos de la fermeture de ressources manuellement, etc.

Un iteratee solution

Première de la lecture du fichier de trucs (qui devraient vraiment être une partie de l'iteratee paquet, qui actuellement ne fournit pas de quelque chose à distance de ce haut-niveau):

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

Et puis notre phrase lecteur:

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

Et enfin, notre analyse de l'action:

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

On peut démontrer que cela fonctionne:

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), (.,.))

Et nous avons terminé.

Ce que je veux

Plus ou moins le même programme mis en œuvre à l'aide de Scalaz des ruisseaux au lieu de iteratees.

49voto

Apocalisp Points 22526

Un scalaz-flux de solution:

import scalaz.std.vector._
import scalaz.syntax.traverse._
import scalaz.std.string._

val action = linesR("example.txt").map(_.trim).
  splitOn("").flatMap(_.traverseU { _.split(" ") match {
    case Array(form, pos) => emit(form -> pos)
    case pieces =>
      wrap(fail(new Exception("Invalid input %s".format(pieces.mkString(" ")))))
  }})

On peut démontrer que cela fonctionne:

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

Et nous avons terminé.

L' traverseU fonction est une commune de Scalaz combinator. Dans ce cas, il est utilisé pour parcourir, en Process de l'errance, de la phrase, Vector généré par splitOn. Il est équivalent à map suivie par sequence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X