Подтвердить что ты не робот

Сгладить 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))

И как вы могли бы обрабатывать несколько сбоев в коллекции?

4b9b3361

Ответ 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))