2016-09-13 9 views
2

Следующий код для чтения и сопоставления строк файла работает нормально:scalaz, читать и отображать строки файла

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 с, если исполнение идет хорошо. Но программа выдает исключение, если файл не найден, или какая-либо строка содержит буквы.

Как я могу преобразовать программу для использования монадов-скаразов и получить 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) 

ответ

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) 

EDIT

Due присутствии всех этих try и catch, если вы хотите продолжать использовать, что monadic-for я хотел бы предложить, чтобы проверить a 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 монаду. Использование монадного трансформатора, такого как ListT или DisjunctionT, здесь возможно, но, вероятно, перебор.

EDIT - ответить Закомментируйте

Как упоминалось выше, если мы хотим, чтобы один monadic-for понимание, нам нужно монады трансформатор, в этом случае 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)

EDIT - комментарий о компиляции выдает

Код выше компилируется благодаря плагину Тип проектора:

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 
+0

прохладно для 'Disjunction.fromTryCatchThrowable'. Хорошо, я вижу проблему (от строк к строке), но что такое решение? Я обновил вопрос, чтобы сделать задачу более ясной. –

+0

@DavidPortabella Я ответил, редактируя свой ответ. –

+0

ничего себе, это работает! (программа не компилировалась, но она была быстро исправлена. Я добавил ваш ответ (исправленный) в конец моего вопроса. Я удалю его из моего вопроса, если вы сможете исправить его в своем ответе). –

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 {линии => линии} неправильно. Компилятор предупреждает с таким сообщением «Список [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(); 
} 

Или вы можете проверить функцию Readline из исходного класса.

+0

' def toInt (строки: List [String]): Disjunction [Any, Int] 'не имеет смысла, поскольку он должен возвращать' Disjunction [Any, List [Int]] '. но тогда «yield (n * 2)» нужно было бы преобразовать в 'yield (n.map (_ * 2))', что делает вещи нечитаемыми. Я обновил вопрос, чтобы сделать задачу более ясной. –

+0

Я только что изменил код. Проверьте это, если это, если вы ожидаете – EmiCareOfCell44