【问题标题】:File I/O with Free Monads使用免费 Monad 的文件 I/O
【发布时间】:2017-12-30 16:50:41
【问题描述】:

我有一个 CSV 文件,我需要对其进行解析并对每条记录执行一些操作。我如何使用 Free Monads?目前,我正在将整个文件加载到内存中,并想知道是否有更好的解决方案。下面是我的程序:

for {
    reader <- F.getReader("my_file.csv")
    csvRecords <- C.readCSV(reader)
    _ <- I.processCSV(csvRecords)
    _ <- F.close(reader)
} yield()

此代码适用于较小的文件,但如果我有非常大的文件(超过 1 GB),这将无法很好地工作。我使用Commons CSV 来读取CSVRecords。

【问题讨论】:

  • arjunswaj,你真正需要什么:一种使用 Free Monad 的方法或任何有效且不会将整个文件加载到内存中的解决方案?
  • @SergGr 我想要一个使用 Free Monad 并且不会将整个文件加载到内存中的方法。
  • 好的,免费的单子是强制性的,你能告诉我们你目前的readCSVprocessCSV吗? CSVParser 似乎已经提供了Iterable/Iterator API
  • @SergGr, here 是我所拥有的程序的要点。
  • 我想以 FP 方式做更多事情——不使用带有 Iterable/Iterator 的 hasNext/next 的 while 循环。也许通过使用流。在上面的当前实现中,流是从 List 创建的,并且 list 将文件中的所有记录加载到内存中。

标签: scala dsl scalaz scala-cats free-monad


【解决方案1】:

根据您的要点查看代码,我认为带有注释的行正是您根本不想要的行:

  object CSVIOInterpreter extends (CSVIO ~> Future) {
    import scala.collection.JavaConverters._
    override def apply[A](fa: CSVIO[A]): Future[A] = fa match {
      case ReadCSV(reader) => Future.fromTry(Try {
        CSVFormat.RFC4180
          .withFirstRecordAsHeader()
          .parse(reader)
          .getRecords // Loads the complete file
          .iterator().asScala.toStream
      })
    }
  }

只需删除整个 getRecords 行。 CSVFormat.parse 返回一个 CSVParser 的实例,它已经实现了 Iterable&lt;CSVRecord&gt;。而getRecords 调用是唯一强制它读取整个文件的方法。

其实你可以看到CSVParser.getRecords的实现,就是

 public List<CSVRecord> getRecords() throws IOException {
     CSVRecord rec;
     final List<CSVRecord> records = new ArrayList<>();
     while ((rec = this.nextRecord()) != null) {
         records.add(rec);
     }
     return records;
 }

所以它只是使用 this.nextRecord 调用来实现整个文件,这显然是 API 的更“核心”部分。

因此,当我在没有 getRecords 调用的情况下执行您的代码的简化版本时:

import cats._
import cats.free.Free
import java.io._
import org.apache.commons.csv._
import scala.collection.JavaConverters._

trait Action[A] {
  def run(): A
}

object F {

  import Free.liftF

  case class GetReader(fileName: String) extends Action[Reader] {
    override def run(): Reader = new FileReader(fileName)
  }

  case class CloseReader(reader: Reader) extends Action[Unit] {
    override def run(): Unit = reader.close()
  }

  def getReader(fileName: String): Free[Action, Reader] = liftF(GetReader(fileName))

  def close(reader: Reader): Free[Action, Unit] = liftF(CloseReader(reader))
}

object C {

  import Free.liftF

  case class ReadCSV(reader: Reader) extends Action[CSVParser] {
    override def run(): CSVParser = CSVFormat.DEFAULT.parse(reader)
  }

  def readCSV(reader: Reader): Free[Action, CSVParser] = liftF(ReadCSV(reader))
}

object I {

  import Free.liftF

  case class ProcessCSV(parser: CSVParser) extends Action[Unit] {
    override def run(): Unit = {
      for (r <- parser.asScala)
        println(r)
    }
  }

  def processCSV(parser: CSVParser): Free[Action, Unit] = liftF(ProcessCSV(parser))

}

object Runner {

  import cats.arrow.FunctionK
  import cats.{Id, ~>}

  val runner = new (Action ~> Id) {
    def apply[A](fa: Action[A]): Id[A] = fa.run()
  }

  def run[A](free: Free[Action, A]): A = {
    free.foldMap(runner)
  }
}


def test() = {
  val free = for {
    //        reader <- F.getReader("my_file.csv")
    reader <- F.getReader("AssetsImportCompleteSample.csv")
    csvRecords <- C.readCSV(reader)
    _ <- I.processCSV(csvRecords)
    _ <- F.close(reader)
  } yield ()

  Runner.run(free)
}

它似乎在逐行模式下工作正常。

【讨论】:

  • 很好的实现!
【解决方案2】:

这里是我如何使用 CSV 文件来读取和执行一些操作 - 我用scala.io.Source.fromFile()

我创建了一个case class CSV 文件header 类型的case class,以使数据更易于访问和操作。

PS:我不了解 monad,而且我是 Scala 的初学者。我发布了这个,因为它可能会有所帮助。

case class AirportData(id:Int, ident:String, name:String, typeAirport:String, latitude_deg:Double,
longitude_deg:Double, elevation_ft:Double, continent:String, iso_country:String, iso_region:String,
municipality:String)

object AirportData extends App {

def toDoubleOrNeg(s: String): Double = {
  try {
    s.toDouble
   } catch {
    case _: NumberFormatException => -1 
   }
 }

val source = scala.io.Source.fromFile("resources/airportData/airports.csv")
val lines = source.getLines().drop(1)
val data = lines.flatMap { line =>
val p = line.split(",")
  Seq(AirportData(p(0).toInt, p(1).toString, p(2).toString, p(3).toString, toDoubleOrNeg(p(4)), toDoubleOrNeg(p(5)), 
      toDoubleOrNeg(p(6)), p(7).toString, p(8).toString, p(9).toString, p(10).toString))
 }.toArray   
 source.close()
 println(data.length)
 data.take(10) foreach println
}

【讨论】:

    猜你喜欢
    • 2014-08-09
    • 1970-01-01
    • 2014-04-03
    • 1970-01-01
    • 2018-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-01
    相关资源
    最近更新 更多