Сгладить Scala Попробовать
Есть ли простой способ сгладить коллекцию попыток дать либо успех значений try, либо просто отказ?
Например:
def map(l:List[Int]) = l map {
case 4 => Failure(new Exception("failed"))
case i => Success(i)
}
val l1 = List(1,2,3,4,5,6)
val result1 = something(map(l1))
result1: Failure(Exception("failed"))
val l2 = List(1,2,3,5,6)
val result2 = something(map(l2))
result2: Try(List(1,2,3,5,6))
И как вы могли бы обрабатывать несколько сбоев в коллекции?
Ответы
Ответ 1
Возможно, не так просто, как вы надеялись, но это работает:
def flatten[T](xs: Seq[Try[T]]): Try[Seq[T]] = {
val (ss: Seq[Success[T]]@unchecked, fs: Seq[Failure[T]]@unchecked) =
xs.partition(_.isSuccess)
if (fs.isEmpty) Success(ss map (_.get))
else Failure[Seq[T]](fs(0).exception) // Only keep the first failure
}
val xs = List(1,2,3,4,5,6)
val ys = List(1,2,3,5,6)
println(flatten(map(xs))) // Failure(java.lang.Exception: failed)
println(flatten(map(ys))) // Success(List(1, 2, 3, 5, 6))
Обратите внимание, что использование partition
не является безопасным по типу, как оно есть, о чем свидетельствуют аннотации @unchecked
. В этом отношении лучше будет , который накапливает две последовательности Seq[Success[T]]
и Seq[Failure[T]]
.
Если вы хотите сохранить все сбои, вы можете использовать это:
def flatten2[T](xs: Seq[Try[T]]): Either[Seq[T], Seq[Throwable]] = {
val (ss: Seq[Success[T]]@unchecked, fs: Seq[Failure[T]]@unchecked) =
xs.partition(_.isSuccess)
if (fs.isEmpty) Left(ss map (_.get))
else Right(fs map (_.exception))
}
val zs = List(1,4,2,3,4,5,6)
println(flatten2(map(xs))) // Right(List(java.lang.Exception: failed))
println(flatten2(map(ys))) // Left(List(1, 2, 3, 5, 6))
println(flatten2(map(zs))) // Right(List(java.lang.Exception: failed,
// java.lang.Exception: failed))
Ответ 2
Это очень близко к минимальному для первой операции:
def something[A](xs: Seq[Try[A]]) =
Try(xs.map(_.get))
(до того момента, когда вы не должны беспокоиться о создании метода, просто используйте Try
). Если вы хотите все сбои, метод разумен; Я бы использовал Either
:
def something[A](xs: Seq[Try[A]]) =
Try(Right(xs.map(_.get))).
getOrElse(Left(xs.collect{ case Failure(t) => t }))
Ответ 3
Немного менее подробный и более безопасный тип:
def sequence[T](xs : Seq[Try[T]]) : Try[Seq[T]] = (Try(Seq[T]()) /: xs) {
(a, b) => a flatMap (c => b map (d => c :+ d))
}
Результаты:
sequence(l1)
res8: scala.util.Try [Seq [Int]] = Failure (java.lang.Exception: failed)
sequence(l2)
res9: scala.util.Try [Seq [Int]] = Успех (список (1, 2, 3, 5, 6))
Ответ 4
В качестве дополнения к Импрессивному ответу и комментарию, если у вас есть scalaz-seven и scalaz-contrib/scala210 в ваших зависимостях:
> scala210/console
[warn] Credentials file /home/folone/.ivy2/.credentials does not exist
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.0 (OpenJDK 64-Bit Server VM, Java 1.7.0_17).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import scala.util._
import scala.util._
scala> def map(l:List[Int]): List[Try[Int]] = l map {
| case 4 => Failure(new Exception("failed"))
| case i => Success(i)
| }
map: (l: List[Int])List[scala.util.Try[Int]]
scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._
scala> import scalaz.contrib.std.utilTry._
import scalaz.contrib.std.utilTry._
scala> val l1 = List(1,2,3,4,5,6)
l1: List[Int] = List(1, 2, 3, 4, 5, 6)
scala> map(l1).sequence
res2: scala.util.Try[List[Int]] = Failure(java.lang.Exception: failed)
scala> val l2 = List(1,2,3,5,6)
l2: List[Int] = List(1, 2, 3, 5, 6)
scala> map(l2).sequence
res3: scala.util.Try[List[Int]] = Success(List(1, 2, 3, 5, 6))
Вам нужен скалаз, чтобы получить Applicative
экземпляр для List
(скрытый в экземпляре MonadPlus
), чтобы получить sequence
. Для Traverse
экземпляра Try
вам нужен файл scalaz-contrib, который требуется подписи типа sequence
.
Try
живет за пределами scalaz, так как он появился только в scala 2.10, а scalaz нацелен на кросс-компиляцию в более ранние версии).
Ответ 5
Посмотрите на лифте. С помощью функции конструктора tryo
он дает вам именно ту абстракцию, которую вы ищете.
С помощью tryo
вы можете поднять функцию в Box
. Затем в поле появляется результат из функции или содержит ошибку. Затем вы можете получить доступ к ящику с помощью обычных монадических вспомогательных функций (flatMap, filter и т.д.), Не беспокоясь, если в поле содержится ошибка или результат формирует функцию.
Пример:
import net.liftweb.util.Helpers.tryo
List("1", "2", "not_a_number") map (x => tryo(x.toInt)) map (_ map (_ + 1 ))
Результаты
List[net.liftweb.common.Box[Int]] =
List(
Full(2),
Full(3),
Failure(For input string: "not_a_number",Full(java.lang.NumberFormatException: For input string: "not_a_number"),Empty)
)
Вы можете пропустить ошибочные значения с помощью flatMap
List("1", "2", "not_a_number") map (x => tryo(x.toInt)) flatMap (_ map (_ + 1 ))
Результаты
List[Int] = List(2, 3)
Существует несколько других вспомогательных методов, например. для объединения ящиков (в то время как сообщения об ошибках цепочки). Вы можете найти хороший обзор здесь: Box Cheat Sheet для лифта
Вы можете использовать его самостоятельно, не нужно использовать всю рамку лифта. В приведенных выше примерах я использовал описание sbt script:
scalaVersion := "2.9.1"
libraryDependencies += "net.liftweb" %% "lift-common" % "2.5-RC2"
libraryDependencies += "net.liftweb" %% "lift-util" % "2.5-RC2"
Ответ 6
Это мои 2cents:
def sequence[A, M[_] <: TraversableOnce[_]](in: M[Try[A]])
(implicit cbf:CanBuildFrom[M[Try[A]], A, M[A]]): Try[M[A]] = {
in.foldLeft(Try(cbf(in))) {
(txs, tx) =>
for {
xs <- txs
x <- tx.asInstanceOf[Try[A]]
} yield {
xs += x
}
}.map(_.result())
}
Ответ 7
Начиная с Scala 2.13
, большинство коллекций снабжено методом Either[A1,A2]):(CC[A1],CC[A2]) rel="nofollow noreferrer"> partitionMap
который разделяет элементы на основе функции, которая возвращает либо Right
либо Left
.
В нашем случае мы можем вызвать partitionMap
с функцией, которая преобразует наш Try
в Either
( Try::toEither
), чтобы разделить Success
es как Right
и Failure
как Left
s.
Тогда это просто вопрос соответствия результирующего разделенного кортежа левых и прав на основе того, есть ли левые:
tries.partitionMap(_.toEither) match {
case (Nil, rights) => Success(rights)
case (firstLeft :: _, _) => Failure(firstLeft)
}
// * val tries = List(Success(10), Success(20), Success(30))
// => Try[List[Int]] = Success(List(10, 20, 30))
// * val tries = List(Success(10), Success(20), Failure(new Exception("error1")))
// => Try[List[Int]] = Failure(java.lang.Exception: error1)
Подробная информация о промежуточном шаге partitionMap
Map:
List(Success(10), Success(20), Failure(new Exception("error1"))).partitionMap(_.toEither)
// => (List[Throwable], List[Int]) = (List(java.lang.Exception: error1), List(10, 20))