scala – 在没有错误状态的iteratee库中处理异常
我正在尝试编写一个枚举器,用于使用
Scalaz 7的iteratee库从java.io.BufferedReader逐行读取文件,该库目前只为java.io.Reader提供(极慢)枚举器.
我遇到的问题与我使用的所有其他iteratee库(例如,Haskell的Play 2.0’s和John Millikin’s 我目前的实施 这是我现在拥有的.首先是一些导入和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助手,在枚举器中对其进行建模(松散地,可能是错误的): 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],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],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))) } ) } 现在假设我想将一个包含至少二十五个’0’字符的文件中的所有行收集到一个列表中.我可以写: val action: IO[ErrorOr[List[String]]] = ( I.consume[ErrorOr[String],List] %= I.filter(_.fold(_ => true,_.count(_ == '0') >= 25)) &= enumFile(new File("big.txt")) ).run.map(_.sequence) 在许多方面,这似乎工作得非常漂亮:我可以使用unsafePerformIO解决问题,它将在几分钟内完成数千万行数据和千兆字节数据,在恒定内存中并且不会烧掉堆栈,然后关闭读者完成后.如果我给它一个不存在的文件的名称,它将尽职尽责地给我回复包含在Left中的异常,并且如果enumBuffered在读取时遇到异常,则至少看起来表现得恰当. 潜在问题 我对我的实现有一些担忧 – 特别是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中使用枚举器尝试相同的操作,结果更像我期望的结果: ...> run $tryIO (putStrLn "a") >> tryIO (error "!") >> tryIO (putStrLn "b") a Left ! 我只是没有看到在iteratee库本身没有错误状态的情况下获得此行为的方法. 我的问题 我并不认为他是迭代者的任何专家,但我在一些项目中使用了各种Haskell实现,感觉我或多或少了解基本概念,并曾与Oleg喝咖啡一次.不过,我在这里不知所措.在没有错误状态的情况下,这是处理异常的合理方法吗?有没有办法实现更像枚举器版本的tryIO?我的实现行为有何不同,是否有某种定时炸弹等着我? 解决方法
编辑这里是真正的解决方案.我离开了原帖,因为我认为看到这个模式是值得的.什么适用于Klesli为IterateeT工作
import java.io.{ BufferedReader,effect._,_ } 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,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,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,A]) => tryIO(openFile(f)).flatMap(reader => I.iterateeT[String,A]( EitherT( enumBuffered(reader).apply(s).value.run.ensuring(closeReader(reader))))) } def main(args: Array[String]) { val action = ( I.consume[String,List] %= I.filter(a => a.count(_ == '0') >= 25) &= enumFile(new File(args(0)))).run.run println(action.unsafePerformIO().map(_.size)) } } =====原帖===== 我觉得你需要一个混合的EitherT.如果没有EitherT,你最终只会得到3左派或者权利.使用EitherT,它将适合左侧. 我想你真正想要的是 type ErrorOr[+A] = EitherT[IO,A] I.iterateeT[A,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.KleisliFunctions$$anon$18@be41b41 scala> for { a <- res11; b <- res12 } yield (a,b) res15: scalaz.Kleisli[scalaz.Scalaz.Id,(scalaz./[String,Int],Int])] = scalaz.KleisliFunctions$$anon$18@42fd1445 scala> res15.run(1) res16: (scalaz./[String,Int]) = (/-(4),-/(aa)) 在以下代码中,我们使用EitherT而不是使用Id.由于EitherT具有与Either相同的绑定行为,因此我们最终得到了我们想要的内容. scala> type ErrorOr[+A] = EitherT[Id,String,A] defined type alias ErrorOr scala> Kleisli[ErrorOr,Int]((a:Int) => EitherT(4.right[String].point[Id])) res22: scalaz.Kleisli[ErrorOr,Int] = scalaz.KleisliFunctions$$anon$18@58b547a0 scala> Kleisli[ErrorOr,Int]((a:Int) => EitherT("aa".left[Int].point[Id])) res24: scalaz.Kleisli[ErrorOr,Int] = scalaz.KleisliFunctions$$anon$18@342f2ceb scala> for { a <- res22; b <- res24 } yield 2 res25: scalaz.Kleisli[ErrorOr,Int] = scalaz.KleisliFunctions$$anon$18@204eab31 scala> res25.run(2).run res26: scalaz.Scalaz.Id[scalaz./[String,Int]] = -/(aa) 你可以用IterateeT替换Keisli,用IO代替Id来获得你需要的东西. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |