以下用于读取和映射文件行的代码可以正常工作:
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)
答案 0 :(得分:2)
要回答问题的第二部分,我只需使用Iterator
方法中的fromFile
:
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[_]
显然只有一个。我们需要处理这个“额外类型参数”,例如使用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返回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
答案 1 :(得分:0)
首先,编译器会警告您正在使用混合类型的理解。您的代码由编译器转换为:
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 =&gt; line} 是错误的。编译器会发出类似“ List [Nothing] required”的消息:scalaz。\ / [Any,Int] “因为将列表视为一个没有参数的函数而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类测试readline函数。