#scala
#scala
Вопрос:
Если у меня есть Seq
, я могу map
изменить его.
val ss = Seq("1", "2", "3")
println(ss.map(s => s.toInt)) // List(1, 2, 3)
Но иногда функция, которую вы передаете map
, может завершиться сбоем.
val ss = Seq("1", "2", "c")
println(ss.map(s => try { Success(s.toInt) } catch { case e: Throwable => Failure(e) })) // List(Success(1), Success(2), Failure(java.lang.NumberFormatException: For input string: "c"))
Этот последний вернет Seq[Try[Int]]
. Однако чего я действительно хочу, так это a Try[Seq[Int]]
, где, если какое-либо из отображений является a Failure
, оно останавливает итерацию и вместо этого возвращает Failure
. Если ошибки нет, я хочу, чтобы он просто вернул все преобразованные элементы, распакованные из Try
.
Каков идиоматический способ Scala сделать это?
Ответ №1:
Возможно, вы слишком много думаете об этом. Анонимная функция в вашем map
по сути такая же, как Try.apply
. Если вы хотите в конечном итоге получить Try[Seq[Int]]
, то вы можете обернуть Seq
в Try.apply
и map
внутри:
scala> val ss = Try(Seq("1", "2", "c").map(_.toInt))
ss: scala.util.Try[Seq[Int]] = Failure(java.lang.NumberFormatException: For input string: "c")
Если какой-либо из toInt
-х завершится с ошибкой, он выдаст исключение, остановит выполнение и станет Failure
.
Комментарии:
1. Ах, это круто, спасибо. Но что, если бы вместо
_.toInt
я использовал функцию, которая возвращала быTry[B]
вместо фактического генерирования исключения? Это тоже сработает? Я новичок в Scala и, похоже, не могу найтиapply
в документах ^_^;2. @math4tots Щелкните большие буквы «C» и «O» в scaladoc, чтобы переключаться между классом и сопутствующим объектом: T):scala.util.Try[T]» rel=»nofollow noreferrer»> scala-lang.org/api/current /…
Ответ №2:
Не уверен, что это идиоматично, но я бы сделал что-то вроде этого:
import util.{Try, Success, Failure}
import collection.mutable.ListBuffer
def toInt(s: String) =
// Correct usage would be Try(s.toInt)
try {
Success(s.toInt)
}
catch {
case e: Throwable => Failure(e)
}
def convert[A](ss: Seq[String], f: String => Try[A]) = {
ss.foldLeft(Try(ListBuffer[A]())) {
case (a, s) =>
for {
xs <- a
x <- f(s)
}
yield xs : x
}.map(_.toSeq)
}
scala> convert(List("1", "2"), toInt)
scala.util.Try[Seq[Int]] = Success(List(1, 2))
scala> convert(List("1", "c"), toInt)
scala.util.Try[Seq[Int]] = Failure(java.lang.NumberFormatException: For input string: "c")
Если вы действительно хотите завершить работу рано, вместо пропуска элементов вы можете использовать старую добрую рекурсию:
def convert[A](ss: Seq[String], f: String => Try[A]) = {
@annotation.tailrec
def loop(ss: Seq[String], acc: ListBuffer[A]): Try[Seq[A]] = {
ss match {
case h::t =>
f(h) match {
case Success(x) => loop(t, acc : x)
case Failure(e) => Failure(e)
}
case Nil =>
Success(acc.toSeq)
}
}
loop(ss, ListBuffer[A]())
}
Комментарии:
1. Спасибо, вы единственный, кто на самом деле пытается ответить на суть моего вопроса. Я также думал об использовании
foldLeft
, но я надеялся, что преобразование iterable of tries в try of iterables является распространенным шаблоном в функциональном программировании.2. Это хорошо известный шаблон в FP, только
sequence
с Scalaz он не работаетTry
, посколькуOption
это было быList(1.some, 2.some).sequence //> Option[List[Int]] = Some(List(1, 2))
так.