scalaz,读取并映射文件的行
scalaz, read and map the lines of a file
以下读取和映射文件行的代码工作正常:
def readLines(fileName: String) = scala.io.Source.fromFile(fileName).getLines
def toInt(line: String) = line.toInt
val numbers: Iterator[Int] = readLines("/tmp/file.txt").map(toInt).map(_ * 2)
println(numbers.toList)
如果执行顺利,我会得到一个 Int
s 的迭代器。但是如果找不到文件,或者某行包含字母,程序会抛出异常。
如何转换程序以使用 scalaz monad 并获得 Disjunction[Exception, List[Int]]
?
我在 scalaz 7.2.6 上试过这个,但它没有编译:
import scalaz.Scalaz._
import scalaz._
def readLines(fileName: String): Disjunction[Any, List[String]] =
try { scala.io.Source.fromFile(fileName).getLines.toList.right }
catch { case e: java.io.IOException => e.left}
def toInt(line: String): Disjunction[Any, Int] =
try { line.toInt.right }
catch { case e: NumberFormatException => e.left}
val numbers: Disjunction[Any, Int] = for {
lines: List[String] <- readLines("/tmp/file.txt")
line: String <- lines
n: Int <- toInt(line)
} yield (n * 2)
编译失败并出现以下错误:
Error:(89, 37) could not find implicit value for parameter M: scalaz.Monoid[Any]
lines: List[String] <- readLines("/tmp/file.txt")
Error:(89, 37) not enough arguments for method filter: (implicit M: scalaz.Monoid[Any])scalaz.\/[Any,List[String]].
Unspecified value parameter M.
lines: List[String] <- readLines("/tmp/file.txt")
Error:(91, 20) could not find implicit value for parameter M: scalaz.Monoid[Any]
n: Int <- toInt(line)
Error:(91, 20) not enough arguments for method filter: (implicit M: scalaz.Monoid[Any])scalaz.\/[Any,Int].
Unspecified value parameter M.
n: Int <- toInt(line)
我不明白这些错误。有什么问题?
以及如何改进此代码,使其不会将所有文件读入内存,而是一次读取并映射每一行?
更新:Filippo 的回答
import scalaz._
def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {
scala.io.Source.fromFile(fileName).getLines.toList
}
def toInt(line: String) = \/.fromTryCatchThrowable[Int, NumberFormatException](line.toInt)
type λ[+A] = Exception \/ A
val numbers = for {
line: String <- ListT[λ, String](readLines("/tmp/file.txt"))
n: Int <- ListT[λ, Int](toInt(line).map(List(_)))
} yield n * 2
println(numbers)
为了回答你问题的第二部分,我会简单地使用 fromFile
方法中的 Iterator
:
val lines: Iterator[String] = scala.io.Source.fromFile(fileName).getLines
如果你想用toInt
把String
转换成Int
:
import scala.util.Try
def toInt(line: String): Iterator[Int] =
Try(line.toInt).map(Iterator(_)).getOrElse(Iterator.empty)
然后 numbers
可能看起来像:
val numbers = readLines("/tmp/file.txt").flatMap(toInt).map(_ * 2)
编辑
由于所有这些 try
和 catch
的存在,如果您想继续使用 monadic-for
我建议您检查一个 scalaz
助手,例如 .fromTryCatchThrowable
在 Disjunction
:
import scalaz._, Scalaz._
def readLines(fileName: String): Disjunction[Exception, List[String]] =
Disjunction.fromTryCatchThrowable(scala.io.Source.fromFile(fileName).getLines.toList)
def toInt(line: String): Disjunction[Exception, Int] =
Disjunction.fromTryCatchThrowable(line.toInt)
现在我们也有 Exception
而不是 Any
作为左类型。
val numbers = for {
lines: List[String] <- readLines("/tmp/file.txt")
line: String <- lines // The problem is here
n: Int <- toInt(line)
} yield n * 2
这个 monadic-for
的问题是第一行和第三行使用 Disjunction
上下文,而第二行使用 List
monad。在这里使用像 ListT
或 DisjunctionT
这样的 monad 转换器是可能的,但可能有点矫枉过正。
编辑 - 回复评论
如前所述,如果我们想要单个 monadic-for
理解,我们需要一个 monad 转换器,在本例中为 ListT
。 Disjunction
有两个类型参数,而 Monad M[_]
显然只有一个。我们需要处理这个 "extra type parameter",例如使用 type lambda
:
def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {
fromFile(fileName).getLines.toList
}
val listTLines = ListT[({type λ[+a] = Exception \/ a})#λ, String](readLines("/tmp/file.txt"))
listTLines
的类型是什么? ListT
转换器:ListT[\/[Exception, +?], String]
原 for-comprehension
的最后一步是 toInt
:
def toInt(line: String) = \/.fromTryCatchThrowable[Int, NumberFormatException](line.toInt)
val listTNumber = ListT[\/[Exception, +?], Int](toInt("line"))
listTNumber
的类型是什么?它甚至无法编译,因为 toInt return 是 Int
而不是 List[Int]
。我们需要一个 ListT
来连接 for-comprehension
,一个技巧是将 listTNumber
更改为:
val listTNumber = ListT[\/[Exception, +?], Int](toInt("line").map(List(_)))
现在我们有两个步骤:
val numbers = for {
line: String <- ListT[\/[Exception, +?], String](readLines("/tmp/file.txt"))
n: Int <- ListT[\/[Exception, +?], Int](toInt(line).map(List(_)))
} yield n * 2
scala> numbers.run.getOrElse(List.empty) foreach println
2
20
200
如果您想知道为什么展开所有这些:
scala> val unwrap1 = numbers.run
unwrap1: scalaz.\/[Exception,List[Int]] = \/-(List(2, 20, 200))
scala> val unwrap2 = unwrap1.getOrElse(List())
unwrap2: List[Int] = List(2, 20, 200)
scala> unwrap2 foreach println
2
20
200
(假设示例文件包含以下行:1、10、100)
编辑 - 关于编译问题的评论
感谢 Kind Projector 插件,上面的代码可以编译:
addCompilerPlugin("org.spire-math" % "kind-projector_2.11" % "0.5.2")
使用 Kind Projector 我们可以拥有像这样的匿名类型:
Either[Int, +?] // equivalent to: type R[+A] = Either[Int, A]
而不是:
type IntOrA[A] = Either[Int, A]
// or
({type L[A] = Either[Int, A]})#L
首先,编译器会提醒您正在使用理解混合类型。您的代码由编译器转换为:
readLines("/tmp/file.txt") flatMap { lines => lines } map { line => toInt(line) }
flatMap的定义是:
def flatMap[A,B](ma: F[A])(f: A => F[B]): F[B]
在你的例子中 F 是 \/,而这个 flatMap { lines => lines } 是错误的。编译器会发出类似这样的消息“List[Nothing] required: scalaz.\/[Any,Int]”,因为将 list 视为一个没有参数的函数,将 List[Nothing] 视为结果类型。像这样更改您的代码:
import scalaz.Scalaz._
import scalaz._
def readLines(fileName: String): Disjunction[Any, List[String]] =
try { scala.io.Source.fromFile(fileName).getLines.toList.right }
catch { case e: java.io.IOException => e.left}
def toInt(line: List[String]): Disjunction[Any, List[Int]] =
try { (line map { _ toInt }).right }
catch { case e: NumberFormatException => e.left}
val numbers = for {
lines <- readLines("/tmp/file.txt")
n <- toInt(lines)
} yield (n map (_ * 2))
有效。
对于逐行读取,也许 FileInputStream 会更容易:
fis = new FileInputStream("/tmp/file.txt");
reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
while(line != null){
System.out.println(line);
line = reader.readLine();
}
或者您可以从 Source class.
测试 readline 函数
以下读取和映射文件行的代码工作正常:
def readLines(fileName: String) = scala.io.Source.fromFile(fileName).getLines
def toInt(line: String) = line.toInt
val numbers: Iterator[Int] = readLines("/tmp/file.txt").map(toInt).map(_ * 2)
println(numbers.toList)
如果执行顺利,我会得到一个 Int
s 的迭代器。但是如果找不到文件,或者某行包含字母,程序会抛出异常。
如何转换程序以使用 scalaz monad 并获得 Disjunction[Exception, List[Int]]
?
我在 scalaz 7.2.6 上试过这个,但它没有编译:
import scalaz.Scalaz._
import scalaz._
def readLines(fileName: String): Disjunction[Any, List[String]] =
try { scala.io.Source.fromFile(fileName).getLines.toList.right }
catch { case e: java.io.IOException => e.left}
def toInt(line: String): Disjunction[Any, Int] =
try { line.toInt.right }
catch { case e: NumberFormatException => e.left}
val numbers: Disjunction[Any, Int] = for {
lines: List[String] <- readLines("/tmp/file.txt")
line: String <- lines
n: Int <- toInt(line)
} yield (n * 2)
编译失败并出现以下错误:
Error:(89, 37) could not find implicit value for parameter M: scalaz.Monoid[Any]
lines: List[String] <- readLines("/tmp/file.txt")
Error:(89, 37) not enough arguments for method filter: (implicit M: scalaz.Monoid[Any])scalaz.\/[Any,List[String]].
Unspecified value parameter M.
lines: List[String] <- readLines("/tmp/file.txt")
Error:(91, 20) could not find implicit value for parameter M: scalaz.Monoid[Any]
n: Int <- toInt(line)
Error:(91, 20) not enough arguments for method filter: (implicit M: scalaz.Monoid[Any])scalaz.\/[Any,Int].
Unspecified value parameter M.
n: Int <- toInt(line)
我不明白这些错误。有什么问题?
以及如何改进此代码,使其不会将所有文件读入内存,而是一次读取并映射每一行?
更新:Filippo 的回答
import scalaz._
def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {
scala.io.Source.fromFile(fileName).getLines.toList
}
def toInt(line: String) = \/.fromTryCatchThrowable[Int, NumberFormatException](line.toInt)
type λ[+A] = Exception \/ A
val numbers = for {
line: String <- ListT[λ, String](readLines("/tmp/file.txt"))
n: Int <- ListT[λ, Int](toInt(line).map(List(_)))
} yield n * 2
println(numbers)
为了回答你问题的第二部分,我会简单地使用 fromFile
方法中的 Iterator
:
val lines: Iterator[String] = scala.io.Source.fromFile(fileName).getLines
如果你想用toInt
把String
转换成Int
:
import scala.util.Try
def toInt(line: String): Iterator[Int] =
Try(line.toInt).map(Iterator(_)).getOrElse(Iterator.empty)
然后 numbers
可能看起来像:
val numbers = readLines("/tmp/file.txt").flatMap(toInt).map(_ * 2)
编辑
由于所有这些 try
和 catch
的存在,如果您想继续使用 monadic-for
我建议您检查一个 scalaz
助手,例如 .fromTryCatchThrowable
在 Disjunction
:
import scalaz._, Scalaz._
def readLines(fileName: String): Disjunction[Exception, List[String]] =
Disjunction.fromTryCatchThrowable(scala.io.Source.fromFile(fileName).getLines.toList)
def toInt(line: String): Disjunction[Exception, Int] =
Disjunction.fromTryCatchThrowable(line.toInt)
现在我们也有 Exception
而不是 Any
作为左类型。
val numbers = for {
lines: List[String] <- readLines("/tmp/file.txt")
line: String <- lines // The problem is here
n: Int <- toInt(line)
} yield n * 2
这个 monadic-for
的问题是第一行和第三行使用 Disjunction
上下文,而第二行使用 List
monad。在这里使用像 ListT
或 DisjunctionT
这样的 monad 转换器是可能的,但可能有点矫枉过正。
编辑 - 回复评论
如前所述,如果我们想要单个 monadic-for
理解,我们需要一个 monad 转换器,在本例中为 ListT
。 Disjunction
有两个类型参数,而 Monad M[_]
显然只有一个。我们需要处理这个 "extra type parameter",例如使用 type lambda
:
def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {
fromFile(fileName).getLines.toList
}
val listTLines = ListT[({type λ[+a] = Exception \/ a})#λ, String](readLines("/tmp/file.txt"))
listTLines
的类型是什么? ListT
转换器:ListT[\/[Exception, +?], String]
原 for-comprehension
的最后一步是 toInt
:
def toInt(line: String) = \/.fromTryCatchThrowable[Int, NumberFormatException](line.toInt)
val listTNumber = ListT[\/[Exception, +?], Int](toInt("line"))
listTNumber
的类型是什么?它甚至无法编译,因为 toInt return 是 Int
而不是 List[Int]
。我们需要一个 ListT
来连接 for-comprehension
,一个技巧是将 listTNumber
更改为:
val listTNumber = ListT[\/[Exception, +?], Int](toInt("line").map(List(_)))
现在我们有两个步骤:
val numbers = for {
line: String <- ListT[\/[Exception, +?], String](readLines("/tmp/file.txt"))
n: Int <- ListT[\/[Exception, +?], Int](toInt(line).map(List(_)))
} yield n * 2
scala> numbers.run.getOrElse(List.empty) foreach println
2
20
200
如果您想知道为什么展开所有这些:
scala> val unwrap1 = numbers.run
unwrap1: scalaz.\/[Exception,List[Int]] = \/-(List(2, 20, 200))
scala> val unwrap2 = unwrap1.getOrElse(List())
unwrap2: List[Int] = List(2, 20, 200)
scala> unwrap2 foreach println
2
20
200
(假设示例文件包含以下行:1、10、100)
编辑 - 关于编译问题的评论
感谢 Kind Projector 插件,上面的代码可以编译:
addCompilerPlugin("org.spire-math" % "kind-projector_2.11" % "0.5.2")
使用 Kind Projector 我们可以拥有像这样的匿名类型:
Either[Int, +?] // equivalent to: type R[+A] = Either[Int, A]
而不是:
type IntOrA[A] = Either[Int, A]
// or
({type L[A] = Either[Int, A]})#L
首先,编译器会提醒您正在使用理解混合类型。您的代码由编译器转换为:
readLines("/tmp/file.txt") flatMap { lines => lines } map { line => toInt(line) }
flatMap的定义是:
def flatMap[A,B](ma: F[A])(f: A => F[B]): F[B]
在你的例子中 F 是 \/,而这个 flatMap { lines => lines } 是错误的。编译器会发出类似这样的消息“List[Nothing] required: scalaz.\/[Any,Int]”,因为将 list 视为一个没有参数的函数,将 List[Nothing] 视为结果类型。像这样更改您的代码:
import scalaz.Scalaz._
import scalaz._
def readLines(fileName: String): Disjunction[Any, List[String]] =
try { scala.io.Source.fromFile(fileName).getLines.toList.right }
catch { case e: java.io.IOException => e.left}
def toInt(line: List[String]): Disjunction[Any, List[Int]] =
try { (line map { _ toInt }).right }
catch { case e: NumberFormatException => e.left}
val numbers = for {
lines <- readLines("/tmp/file.txt")
n <- toInt(lines)
} yield (n map (_ * 2))
有效。
对于逐行读取,也许 FileInputStream 会更容易:
fis = new FileInputStream("/tmp/file.txt");
reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
while(line != null){
System.out.println(line);
line = reader.readLine();
}
或者您可以从 Source class.
测试 readline 函数