2013-03-19 11 views
13

Czy istnieje prosty sposób na spłaszczenie kolekcji prób, aby zapewnić sukces wartości prób, lub po prostu niepowodzenie? Na przykład:Spłaszczaj Scala Wypróbuj

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)) 

A może jak byś obsługiwać wiele niepowodzeń w kolekcji?

+3

Proszę powiedzieć, jak Twój wynik powinien wyglądać. 'Lista (1, 2, 3, Wyjątek, 5, 7)'? –

+1

Scalaz nazwałby tę operację 'sekwencją', ale niestety brakuje jej w standardowej bibliotece. – Impredicative

Odpowiedz

5

Może nie tak proste, jak się spodziewamy, ale to działa:

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)) 

pamiętać, że korzystanie z partition nie jest tak bezpieczne, jak to robi, o czym świadczą @unchecked adnotacji typu. Pod tym względem lepszy byłby kod foldLeft, który gromadzi dwie sekwencje Seq[Success[T]] i Seq[Failure[T]].

Jeśli chcesz zachować wszystkie awarie, można użyć to:

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

+1 "W tym względzie, fałdLeft, który gromadzi dwie sekwencje Seq [Sukces [T]] i Seq [Niepowodzenie [T]] będzie lepszy." Tak! W ekosystemie scala występuje wiele typów walidacji lub niepowodzeń (Option, Try, Scalaz.Disjunction, scalaz.Validation, ...), a mimo to nie mogłem znaleźć takich podwójnych przykładów wśród publicznych metod i wspólne wzorce. – rloth

9

trochę mniej gadatliwy i bezpieczną więcej typ:

def sequence[T](xs : Seq[Try[T]]) : Try[Seq[T]] = (Try(Seq[T]()) /: xs) { 
    (a, b) => a flatMap (c => b map (d => c :+ d)) 
} 

wyniki:

sequence(l1) 

res8: scala.util.Try [Seq [Int]] = Błąd (wyjątek java.lang.Exception: failed)

sequence(l2) 

res9: scala.util.Try [SEK [Int]] = sukces (Lista (1, 2, 3, 5, 6))

+0

Wygląda ładnie, ale czy nie będzie dość trafienia przy operacji dołączania? –

+1

Fair point - Zapomniałem, że domyślne 'Seq' jest' List'. Dodaj do tyłu/użyj wektora według własnego uznania! – Impredicative

+0

Miło, nie widziałem tego skrótu FoldLeft przed '/:' – Stephen

3

Zobacz na moneta Box podnośnikowy. Przy pomocy funkcji konstruktora tryo daje on dokładnie abstrakcję, której szukasz.

Z tryo można podnieść funkcję do Box. Wówczas pole zawiera wynik z funkcji lub zawiera błąd. Następnie można uzyskać dostęp do skrzynki za pomocą zwykłych monadycznych funkcji pomocniczych (flatMap, filter, itp.), Bez zawracania sobie głowy, jeśli pudełko zawiera błąd lub wynik z funkcji.

Przykład:

import net.liftweb.util.Helpers.tryo 

List("1", "2", "not_a_number") map (x => tryo(x.toInt)) map (_ map (_ + 1)) 

Wyniki na

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) 
) 

można pominąć błędnych wartości z flatMap

List("1", "2", "not_a_number") map (x => tryo(x.toInt)) flatMap (_ map (_ + 1)) 

Wyniki

List[Int] = List(2, 3) 

Istnieje wiele innych metod pomocniczych, np. do łączenia pól (podczas łączenia komunikatów o błędach). Tutaj znajdziesz dobry przegląd: Box Cheat Sheet for Lift

Możesz go używać samodzielnie, bez potrzeby korzystania z całej struktury windy.Na powyższych przykładach użyłem follwing skrypt SBT:

scalaVersion := "2.9.1" 

libraryDependencies += "net.liftweb" %% "lift-common" % "2.5-RC2" 

libraryDependencies += "net.liftweb" %% "lift-util" % "2.5-RC2" 
6

Jako dodatek do odpowiedzi i komentarzy Impredicative, o ile masz zarówno scalaz-seven i scalaz-contrib/scala210 w swoich zależnościach:

> 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)) 

Trzeba scalaz aby uzyskać Applicativeinstance dla List (ukryty w instancji MonadPlus), aby uzyskać metodę sequence. Potrzebujesz skalase-contrib dla Traverseinstance z Try, która jest wymagana przez podpis typu sequence. Try mieszka poza skalazem, ponieważ pojawił się tylko w scala 2.10, a scalaz ma na celu cross-kompilację do wcześniejszych wersji).

24

Jest to dość blisko minimalne dla fail-pierwszej operacji:

def something[A](xs: Seq[Try[A]]) = 
    Try(xs.map(_.get)) 

(do punktu, w którym nie powinno niepokoić stworzenie metody, po prostu wykorzystać Try). Jeśli chcesz wszystkich niepowodzeń, metoda jest rozsądna; Chciałbym użyć Either:

def something[A](xs: Seq[Try[A]]) = 
    Try(Right(xs.map(_.get))). 
    getOrElse(Left(xs.collect{ case Failure(t) => t })) 
+0

Nice! (Mimo, że ponownie rzuca wyjątek, jeśli taki istnieje) Jak rozszerzyłbyś go tak, aby wszystkie wyjątki były zachowane? –

+0

@mhs - Jeśli awaria nie jest rzadkością lub wydajność nie jest krytyczna, potrzebujesz ponownego rzutu, ponieważ jest to najprostszy sposób rozwiązania problemu. Śledzenie stosu wyjątku zostało już wygenerowane, więc rzut/catch nie jest zbyt drogi. Gdybym chciał zachować wszystkie wyjątki, zapakowałbym je w 'albo", np. jak w mojej edycji. –

0

Są to moi 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()) 
    } 
Powiązane problemy