Попробуйте... поймать... наконец вернуть значение
Я обнаружил, что стыдно, что я не могу вернуть возвращаемое значение из такой простой конструкции, как try ... catch ... finally
def foo: String = {
val in = new BufferedReader(.....)
try {
// val in = new BufferedReader(.....) -- doesn't matter
in.readLine
}
catch {
case e: IOException => e.printStackTrace()
}
finally {
in.close()
}
}
Этот код не компилируется. Есть ли способ сделать компиляцию ожидать с помощью любых библиотек, высокоуровневых конструкций и т.д.? Я хочу это сделать, только используя чистую Scala как язык программирования.
Ответы
Ответ 1
В блоке scala try-catch-finally блок finally
оценивается только для побочных эффектов; значение блока в целом является значением последнего выражения в try
(если не было выбрано исключение) или catch
(если таковой был).
Если вы посмотрите на результат компилятора, вы заметите, что он жалуется на содержимое блока catch
, а не на finally
:
$ scala test.scala
/tmp/test.scala:12: error: type mismatch;
found : Unit
required: String
case e: Exception => e.printStackTrace()
Это связано с тем, что Exception.printStackTrace()
возвращает Unit
, поэтому возвращаемый тип функции должен быть String
, если try
и Unit
в противном случае.
Вы можете решить эту проблему, если блок catch
также оценит строку:
catch {
case e: IOException => {
e.printStackTrace()
e.toString()
}
}
Конечно, это означает, что должно быть какое-то полезное строковое значение, которое вы можете вернуть, даже когда возникает ошибка (возможно, ""
?); более идиоматическим подходом может быть возвращение Option[String]
, при этом блок try
возвращает Some(in.readLine)
и блок catch
возвращает None
. В любом случае значение обоих блоков try
и catch
должно соответствовать сигнатуре функции. Тип блока finally
не имеет значения.
Для справки, здесь версия, которая проходит проверку типов и работает:
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.IOException
def foo: String = {
val in = new BufferedReader(new InputStreamReader(System.in))
try {
in.readLine
}
catch {
case e: IOException => { e.printStackTrace(); e.toString() }
}
finally {
in.close()
}
}
System.out.println("Return value: " + foo)
in.close()
возвращает Unit, но это нормально, потому что значение блока finally
игнорируется. try
и catch
блокируют возврату строки.
Ответ 2
Я думаю, что это поможет начать с концепции Java исключения. Метод Java - это, в основном, контракт, чтобы что-то сделать (вернуть значение или вызвать побочный эффект), если он вызван. Метод делает определенные предположения (например, что операционная система будет сотрудничать с запросом на чтение файла). Иногда, если эти условия не выполняются, он возвращает нулевое значение, и иногда он полностью прекратит выполнение и "выкинет исключение".
Это может быть проблемой, поскольку контракт Java-метода не всегда ясен. Метод Java, который объявляет тип возврата String, действительно имеет три результата: значение String, null или исключение. Одна из проблем с исключениями заключается в том, что они прекращают выполнение кода, возможно, маскируя другие проблемы дальше в методе или, возможно, не закрывая открытые ресурсы (что является причиной для try, catch, finally
)
Scala ищет ясность относительно типа возврата. Один из способов сделать это - собрать все исключения, возникающие в методе, а затем передать этот список исключений в качестве возвращаемого значения. Но тогда нам нужно иметь тип возврата, в котором говорится: "Мы что-то вернем, или мы ничего не вернем" (scala.Option), или, возможно, "Мы собираемся вернуть либо ожидаемый ответ, либо мы вернем информацию о том, почему ожидаемый ответ не возвращается" (scala. Или), или, возможно, "Мы постараемся сделать рискованную операцию, которая может привести к успеху или неудаче". (scala.util.Try)
Scala имеет дело с возможностью нулевого значения с помощью опции. Опция - это класс, который имеет два подкласса: None
и Some
, который является контейнером, который содержит ровно один элемент. Например:
val contacts = Map("mark" -> 1235551212, "john" -> 2345551212, "william" -> 3455551212)
val phoneOfJohn: Option[Int] = contacts.get("john")
val phoneOfAlex: Option[Int] = contacts.get("alex")
phoneOfJohn match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call John, can't find him in contacts")
}
phoneOfAlex match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call Alex, can't find him in contacts")
}
Эти коды делают телефонный звонок к Джону, и он будет регистрировать тот факт, что он не смог позвонить Алексу, потому что он не смог найти свой номер телефона в телефонной книге. Но Option
не предоставляет информацию о том, почему значение не было возвращено. Если мы хотим собрать эти причины, мы можем использовать Either
. Either
имеет два подкласса: A Left
может хранить все исключения, которые были собраны в процессе выполнения "рискованной операции", в то время как Right
будет похож на Some
и содержать ожидаемое значение.
Выполнение операции fold для того, чтобы преобразовать ее влево или вправо, несколько противоречит интуиции, и поэтому мы приходим к scala.util.Try.
@marius, разработчик scala в Twitter, написал очень хороший post об обосновании принятия scala.util.Try. Я думаю, это то, что вы ищете.
Сущность scala.util.Try заключается в том, что рискованное действие может привести к Success
или Failure
. До scala.util.Try разработчики использовали опцию или Либо. Вот как это выглядело бы, если бы вы сделали буферизованный читатель из файла:
import scala.util.{Try, Failure, Success}
def foo(fileName: String): Try[String] = Try {
scala.io.Source.fromFile(fileName).bufferedReader().readLine()
}
def bar(file: String): Unit = foo(file) match {
case Success(answer) => Logger.info(s"$file says the answer is $answer")
case Failure(e) => Logger.error(s"couldn't get answer, errors: ${e.getStackTrace}")
}
bar("answer.txt") \\ will log the answer or a stack trace
Надеюсь, это поможет!
Ответ 3
Это происходит потому, что в Scala, в отличие от Java, try-catch - это выражения. В вашем коде, попробуйте блок возвращает String
, а тип возврата блока catch - Unit
(поскольку вы просто печатаете и ничего не возвращаете).
Таким образом, тип-вывод принимает тип возвращаемого значения T
такой, что T >: Unit
и T >: String
. Следовательно, T
имеет тип Any
. И потому, что вы объявили foo как def foo: String
. Компилятор выдает ошибку, так как ожидал String, но нашел Any.
Что вы можете сделать, так это вернуть значение по умолчанию в ваш блок catch:
try{
in.readLine
}
catch {
case e: IOException => {
e.printStackTrace()
"error string"
}
}
finally{
in.close
}
Ответ 4
Этот метод возвращает Unit
; вы должны что-то вернуть в catch catch; или я предпочел бы изменить тип возврата, например. Option
; или используйте класс Try
.
Что очевидно - в этой ситуации у вас не всегда есть строка. В Java люди склонны игнорировать реалии, но это Scala, можно сделать лучше.