David Portabella David Portabella - 1 year ago 118
Scala Question

scalaz, read and map the lines of a file

The following code to read and map the lines of a file works ok:

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)

I get an iterator of
s if the executing goes well. But the program throws an exception if the file is not found, or some line contains letters.

How can I transform the program to use scalaz monads and get a
Disjunction[Exception, List[Int]]

I tried this on scalaz 7.2.6, but it does not compile:

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)

it fails to compile with these errors:

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)

I don't understand the errors. what is the problem?

and how to improve this code, so that it does not read all the file into memory, but it reads and maps each line at a time?

Update: Answer from Filippo

import scalaz._

def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {

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


Answer Source

To answer the second part of your question, I would simply use the Iterator out of the fromFile method:

val lines: Iterator[String] = scala.io.Source.fromFile(fileName).getLines

If you want to use toInt to convert String to Int:

import scala.util.Try

def toInt(line: String): Iterator[Int] =

Then numbers could look like:

val numbers = readLines("/tmp/file.txt").flatMap(toInt).map(_ * 2)


Due the presence of all these try and catch, if you want to keep using that monadic-for I would suggest to check a scalaz helper like .fromTryCatchThrowable on Disjunction:

import scalaz._, Scalaz._

def readLines(fileName: String): Disjunction[Exception, List[String]] =

def toInt(line: String): Disjunction[Exception, Int] =

Now we also have Exception instead of Any as the left type.

val numbers = for {
  lines: List[String] <- readLines("/tmp/file.txt")
  line: String        <- lines                      // The problem is here
  n: Int              <- toInt(line)
} yield n * 2

The problem with this monadic-for is that the first and third line are using the Disjunction context but the second one uses the List monad. Using a monad transformer like ListT or DisjunctionT here is possible but probably overkill.

EDIT - to reply the comment

As mentioned, if we want a single monadic-for comprehension, we need a monad transformer, in this case ListT. The Disjunction has two type parameters while a Monad M[_] obviously only one. We need to handle this "extra type parameter", for instance using type lambda:

def readLines(fileName: String) = \/.fromTryCatchThrowable[List[String], Exception] {

val listTLines = ListT[({type λ[+a] = Exception \/ a})#λ, String](readLines("/tmp/file.txt"))

What is the type of listTLines? The ListT transformer: ListT[\/[Exception, +?], String]

The last step in the original for-comprehension was toInt:

def toInt(line: String) = \/.fromTryCatchThrowable[Int, NumberFormatException](line.toInt)

val listTNumber = ListT[\/[Exception, +?], Int](toInt("line"))

What is the type of listTNumber? It doesn't even compile, because the toInt return an Int and not a List[Int]. We need a ListT to join that for-comprehension, one trick could be changing listTNumber to:

val listTNumber = ListT[\/[Exception, +?], Int](toInt("line").map(List(_)))

Now we have both steps:

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

If you are wondering why all this unwrapping:

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

(assuming that the sample file contains the lines: 1, 10, 100)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download