2012-10-28 28 views
8

Devo ridurre un Iterable [O Throwable, String]] a uno [Throwable, Iterable [String]]. Non so se questa operazione è abbastanza comune o meno, non ho trovato nulla sul tratto Iterable. Così ho scritto questa funzione:Riduzione Iterable [O [A, B]] su [A, Iterable [B]]

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] = 
    xs.collectFirst { 
    case Left(x) => x 
    } match { 
    case Some(x) => Left(x) 
    case None => Right(xs.collect{case Right(y)=> y}) 
    } 

qualcuno mi può aiutare a trovare un modo migliore se questa non lo è?

+0

La trasformazione che si vuole raggiungere è un po 'ambigua. La tua lista di input contiene per esempio la metà di 'Right [String]' se metà di vari ed eterogenei 'Left [Exception]' s. Si vuole ridurlo a una sola eccezione o lista di stringhe. Quale eccezione dovrebbe essere presa se ci fossero, ad es. dieci diversi nell'input? –

+0

Hai ragione. Voglio considerare solo la prima eccezione (o qualsiasi valore Left) che nasconderà altri, ma è accettabile per il mio caso d'uso. –

+0

Questo è un duplicato di http://stackoverflow.com/questions/7230999/how-to-reduce-a-seqeithera-b-to-a-eitherseqa-seqb. – ziggystar

risposta

11

Questa operazione viene spesso chiamato sequenziamento, ed è disponibile nelle librerie standard di alcuni linguaggi funzionali (come Haskell). In Scala puoi implementare il tuo o usare una libreria esterna come Scalaz. Supponiamo di avere il seguente, ad esempio:

val xs: List[Either[String, Int]] = List(Right(1), Right(2)) 
val ys: List[Either[String, Int]] = List(Right(1), Left("1st!"), Left("2nd!")) 

Ora possiamo scrivere (usando Scalaz 7):

scala> import scalaz._, Scalaz._ 
import scalaz._ 
import Scalaz._ 

scala> xs.sequenceU 
res0: Either[String,List[Int]] = Right(List(1, 2)) 

scala> ys.sequenceU 
res1: Either[String,List[Int]] = Left(1st!) 

come desiderato.


Come nota, questa operazione richiede solo che il contenitore esterno sia attraversabile e che il contenitore interno tramite un funtore applicativo. Scalaz fornisce anche una classe ValidationNEL che è un po 'come Either e anche adatta a questi requisiti, ma utilizzando sequence su un elenco di ValidationNEL s raccoglie più errori invece di fermarsi alla prima:

val zs: List[ValidationNEL[String, Int]] = 
    List(1.successNel, "1st".failNel, "2nd".failNel) 

Ora otteniamo:

scala> print(zs.sequenceU) 
Failure(NonEmptyList(1st, 2nd)) 

si potrebbe anche usare sequence su un elenco di Option s, Promise s, ecc

+2

In effetti è molto simile a "Future.sequence" nel framework Akka, non è vero? –

+0

@FilippoDeLuca: Sì, esattamente, quello è solo meno generico di quello di Scalaz. –

+0

Non sono ancora in grado di comprendere appieno lo scalaz, ma devo davvero provarlo, o meglio, deve provarci :) –

2

trovo sempre return dichiarazioni un po 'imbarazzante, ma questa seguenti opere:

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] = 
    Right(xs.collect { 
    case Left(x) => return Left(x) 
    case Right(x) => x 
    }) 
+1

'caso Left (x) => return Left (x)' può essere abbreviato in 'case l @ Left (_) => return l' –

+2

@KimStebel sì L'ho pensato inizialmente, ma il parametro' B' di il risultante 'Either' è sbagliato (richiede' Iterable [B] 'ma invece' B'), quindi il 'Left' è un' Left' diverso. –

+0

ah sì, è vero –

4

Se non ti piace il ritorno esplicito e vuole eliminare il pattern matching, mentre accorciando il codice in qualche modo, qui è un altro versione:

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] = 
    xs collectFirst { 
    case Left(x) => Left(x) 
    } getOrElse Right(xs.flatMap(_.right.toOption)) 
+0

Like it, thank you. –

Problemi correlati