8

Scalaz 7のiterateeライブラリjava.io.BufferedReaderを使用して、ファイルを1行ずつ読み取るための列挙子を作成しようとしています。このライブラリは、現在、の(非常に遅い)列挙子のみを提供します。java.io.Reader

私が遭遇している問題は、私が使用した他のすべてのiterateeライブラリ(たとえば、Play2.0やHaskellのJohnMillikin)が、その型のコンストラクターの1つとしてエラー状態になっているという事実とScalaz7に関連enumeratorStepます。そうではありません。

私の現在の実装

これが私が現在持っているものです。まず、いくつかのインポートとIOラッパーについて:

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

def openFile(f: File) = IO(new BufferedReader(new FileReader(f)))
def readLine(r: BufferedReader) = IO(Option(r.readLine))
def closeReader(r: BufferedReader) = IO(r.close())

そして、物事を少しクリーンアップするためのタイプエイリアス:

type ErrorOr[A] = Either[Throwable, A]

そして今、tryIOヘルパーは、次のものをモデルにしています(大まかに、そしておそらく間違っています)enumerator

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

BufferedReaderそれ自体の列挙子:

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

そして最後に、リーダーの開閉を担当する列挙子:

def enumFile(f: File) = new EnumeratorT[ErrorOr[String], IO] {
  def apply[A] = (s: StepT[ErrorOr[String], IO, A]) => s.mapCont(k =>
    tryIO(openFile(f)) flatMap {
      case Right(reader) => I.iterateeT(
        enumBuffered(reader).apply(s).value.ensuring(closeReader(reader))
      )
      case Left(e) => k(I.elInput(Left(e)))
    }
  )
}

たとえば、少なくとも25'0'文字を含むファイル内のすべての行をリストにまとめたいとします。私は書くことができます:

val action: IO[ErrorOr[List[String]]] = (
  I.consume[ErrorOr[String], IO, List] %=
  I.filter(_.fold(_ => true, _.count(_ == '0') >= 25)) &=
  enumFile(new File("big.txt"))
).run.map(_.sequence)

多くの点で、これは見事に機能しているように見えます。アクションを開始するunsafePerformIOと、数千万行とギガバイトのデータを数分で、一定のメモリ内で、スタックを爆破することなくチャンクし、リーダーを閉じます。終わったら。存在しないファイルの名前を付けると、。でラップされた例外が忠実に返され、読み取り中に例外が発生した場合でもLeftenumBuffered少なくとも適切に動作するように見えます。

潜在的な問題

ただし、特に。の実装について懸念がありtryIOます。たとえば、いくつかの反復を作成しようとしたとします。

val it = for {
  _ <- tryIO[Unit, Unit](IO(println("a")))
  _ <- tryIO[Unit, Unit](IO(throw new Exception("!")))
  r <- tryIO[Unit, Unit](IO(println("b")))
} yield r

これを実行すると、次のようになります。

scala> it.run.unsafePerformIO()
a
b
res11: ErrorOr[Unit] = Right(())

GHCiで同じことを試してみるとenumerator、結果は私が期待するものに似ています。

...> run $ tryIO (putStrLn "a") >> tryIO (error "!") >> tryIO (putStrLn "b")
a
Left !

iterateeライブラリ自体にエラー状態がない状態でこの動作を取得する方法がわかりません。

私の質問

私は反復の専門家であるとは言いませんが、いくつかのプロジェクトでさまざまなHaskellの実装を使用し、基本的な概念を多かれ少なかれ理解しているように感じ、Olegと一度コーヒーを飲みました。しかし、私はここで途方に暮れています。これは、エラー状態がない場合に例外を処理するための合理的な方法ですか?バージョンtryIOのように動作する実装方法はありますか?enumerator私の実装の動作が異なるという事実で、私を待っているある種の時限爆弾はありますか?

4

2 に答える 2

6

ここで編集するのが本当の解決策です。パターンを見る価値があると思うので、元の投稿に残しました。Klesliで機能するものはIterateeTで機能します

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

object IterateeIOExample {
  type ErrorOr[+A] = EitherT[IO, Throwable, A]

  def openFile(f: File) = IO(new BufferedReader(new FileReader(f)))
  def readLine(r: BufferedReader) = IO(Option(r.readLine))
  def closeReader(r: BufferedReader) = IO(r.close())

  def tryIO[A, B](action: IO[B]) = I.iterateeT[A, ErrorOr, B] {
    EitherT.fromEither(action.catchLeft).map(r => I.sdone(r, 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(readLine(reader)) 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(openFile(f)).flatMap(reader => I.iterateeT[String, ErrorOr, A](
        EitherT(
          enumBuffered(reader).apply(s).value.run.ensuring(closeReader(reader)))))
  }

  def main(args: Array[String]) {
    val action = (
      I.consume[String, ErrorOr, List] %=
      I.filter(a => a.count(_ == '0') >= 25) &=
      enumFile(new File(args(0)))).run.run

    println(action.unsafePerformIO().map(_.size))
  }
}

=====元の投稿=====

ミックスにEitherTが必要な気がします。どちらかがなければ、あなたは3つの左または右で終わるだけです。どちらかTを使用すると、左側が適切になります。

あなたが本当に欲しいのは

type ErrorOr[+A] = EitherT[IO, Throwable, A] 
I.iterateeT[A, ErrorOr, B]

次のコードは、現在の構成方法を模倣しています。IterateeTには左右の概念がないため、それを作成すると、IO/Idの束になってしまいます。

scala> Kleisli((a:Int) => 4.right[String].point[Id])
res11: scalaz.Kleisli[scalaz.Scalaz.Id,Int,scalaz.\/[String,Int]] = scalaz.KleisliFunctions$$anon$18@73e771ca

scala> Kleisli((a:Int) => "aa".left[Int].point[Id])
res12: scalaz.Kleisli[scalaz.Scalaz.Id,Int,scalaz.\/[String,Int]] = scalaz.KleisliFunctions$$anon$18@be41b41

scala> for { a <- res11; b <- res12 } yield (a,b)
res15: scalaz.Kleisli[scalaz.Scalaz.Id,Int,(scalaz.\/[String,Int], scalaz.\/[String,Int])] = scalaz.KleisliFunctions$$anon$18@42fd1445

scala> res15.run(1)
res16: (scalaz.\/[String,Int], scalaz.\/[String,Int]) = (\/-(4),-\/(aa))

次のコードでは、Idを使用する代わりに、EitherTを使用しています。EtherTのバインド動作はEitherと同じであるため、最終的には必要なものになります。

scala>  type ErrorOr[+A] = EitherT[Id, String, A]
defined type alias ErrorOr

scala> Kleisli[ErrorOr, Int, Int]((a:Int) => EitherT(4.right[String].point[Id]))
res22: scalaz.Kleisli[ErrorOr,Int,Int] = scalaz.KleisliFunctions$$anon$18@58b547a0

scala> Kleisli[ErrorOr, Int, Int]((a:Int) => EitherT("aa".left[Int].point[Id]))
res24: scalaz.Kleisli[ErrorOr,Int,Int] = scalaz.KleisliFunctions$$anon$18@342f2ceb

scala> for { a <- res22; b <- res24 } yield 2
res25: scalaz.Kleisli[ErrorOr,Int,Int] = scalaz.KleisliFunctions$$anon$18@204eab31

scala> res25.run(2).run
res26: scalaz.Scalaz.Id[scalaz.\/[String,Int]] = -\/(aa)

KeisliをIterateeTに、IdをIOに置き換えて、必要なものを入手できます。

于 2012-11-16T20:13:11.447 に答える
4

pipes型クラスを使用して型クラス構成を行う方法は次のChannelとおりです。

class Channel p where
    {-| 'idT' acts like a \'T\'ransparent proxy, passing all requests further
        upstream, and passing all responses further downstream. -}
    idT :: (Monad m) => a' -> p a' a a' a m r

    {-| Compose two proxies, satisfying all requests from downstream with
        responses from upstream. -}
    (>->) :: (Monad m)
          => (b' -> p a' a b' b m r)
          -> (c' -> p b' b c' c m r)
          -> (c' -> p a' a c' c m r)
    p1 >-> p2 = p2 <-< p1

EitherT...そしてベースコンポジションからリフトされたコンポジションを導き出しました。これは、で導入されたプロキシトランスの原理の特殊なケースであり、pipes-2.4任意の拡張機能で構成を持ち上げることができます。

このリフティングでは、次のタイプEitherTの形状に特化したものを定義する必要があります。ProxyControl.Proxy.Trans.Either

newtype EitherP e p a' a b' b (m :: * -> *) r
  = EitherP { runEitherP :: p a' a b' b m (Either e r) }

この形状の特殊化は、クラスProxyの適切に型指定されたインスタンスを定義できるようにするために必要です。Channelこの点で、ScalaはHaskellよりも柔軟かもしれません。

次に、この特殊なタイプMonadのすべての通常の操作とともに、インスタンス(および他のインスタンス)を再定義します。EitherT

throw :: (Monad (p a' a b' b m)) => e -> EitherP e p a' a b' b m r
throw = EitherP . return . Left

catch
 :: (Monad (p a' a b' b m))
 => EitherP e p a' a b' b m r        -- ^ Original computation
 -> (e -> EitherP f p a' a b' b m r) -- ^ Handler
 -> EitherP f p a' a b' b m r        -- ^ Handled computation
catch m f = EitherP $ do
    e <- runEitherP m
    runEitherP $ case e of
        Left  l -> f     l
        Right r -> right r

これを手にして、次のリフトされたコンポジションインスタンスを定義できます。

-- Given that 'p' is composable, so is 'EitherP e p'
instance (Channel p) => Channel (EitherP e p) where
    idT = EitherP . idT
    p1 >-> p2 = (EitherP .) $ runEitherP . p1 >-> runEitherP . p2

そこで何が起こっているのかを理解するには、次のタイプに従ってください。

p1 :: b' -> EitherP e p a' a b' b m r
p2 :: c' -> EitherP e p b' b c' c m r

runEitherP . p1 :: b' -> p a' a b' b m (Either e r)
runEitherP . p2 :: c' -> p b' b c' c m (Either e r)

-- Use the base composition for 'p'
runEitherP . p1 >-> runEitherP . p2
 :: c' -> p a' a c' c m (Either e r)

-- Rewrap in EitherP
(EitherP . ) $ runEitherP . p1 >-> runEitherP . p2
 :: c' -> EitherP e p a' a c' c m r

これにより、他のステージを中断することなく、特定のステージ内でエラーをスローしてキャッチできます。pipes-2.4アナウンスの投稿からコピーして貼り付けた例を次に示します。

import Control.Monad (forever)
import Control.Monad.Trans (lift)
import Control.Proxy
import Control.Proxy.Trans.Either as E
import Safe (readMay)

promptInts :: () -> EitherP String Proxy C () () Int IO r
promptInts () = recover $ forever $ do
    str <- lift getLine
    case readMay str of
        Nothing -> E.throw "Could not parse an integer"
        Just n  -> liftP $ respond n

recover p =
    p `E.catch` (\str -> lift (putStrLn str) >> recover p)

main = runProxy $ runEitherK $ mapP printD <-< promptInts

結果は次のとおりです。

>>> main
1<Enter>
1
Test<Enter>
Could not parse an integer
Apple<Enter>
Could not parse an integer
5<Enter>
5

iterateeアプローチへの答えも同様です。反復を構成する既存の方法を採用し、それを持ち上げる必要がありますEitherT。型クラスを使用するか、単に新しい合成演算子を定義するかは、あなた次第です。

その他の便利なリンク:

于 2012-11-17T00:19:51.593 に答える