2012-10-28 22 views
8

Potrzebuję zmniejszyć Iterable [Albo [Throwable, String]] do [Throwable, Iterable [String]]. Nie wiem, czy ta operacja jest dość powszechna, czy nie, nie znalazłem nic na temat cechy Iterowalnej. Więc napisałem tę funkcję:Redukujące Iterable [Albo [A, B]] na [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}) 
    } 

może ktoś mi pomóc znaleźć lepszą drogę, jeśli ten nie jest?

+0

transformacji chcesz osiągnąć jest nieco dwuznaczne. Twoja lista wejściowa zawiera na przykład połowę 'Right [String]' s oraz połowę różnych i heterogenicznych 'Left [Exception]' s. Chcesz zmniejszyć do jednego wyjątku lub listy ciągów. Który wyjątek należy zastosować, jeśli np. dziesięć różnych w danych wejściowych? –

+0

Masz rację. Chcę wziąć pod uwagę tylko pierwszy wyjątek (lub dowolną wartość lewą), który ukryje inne, ale jest to dopuszczalne w moim przypadku użycia. –

+0

To jest duplikat http://stackoverflow.com/questions/7230999/how-to-reduce-a-seqeithera-b-to-a-eitherseqa-seqb. – ziggystar

Odpowiedz

11

operacja ta jest często nazywana sekwencjonowanie i jest dostępny w standardowych bibliotekach niektórych języków funkcyjnych (takich jak Haskell). W Scali możesz wdrożyć własne lub użyć zewnętrznej biblioteki, takiej jak Scalaz. Załóżmy, że mamy następujące, na przykład:

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

Teraz możemy napisać (przy użyciu 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!) 

zgodnie z zapotrzebowaniem.


Na marginesie ta operacja wymaga jedynie, aby pojemnik zewnętrzny był przesuwny, a pojemnik wewnętrzny był aplikatorem. Scalaz zapewnia również ValidationNEL klasy, który jest bardzo podobny Either a także spełnia te wymagania, ale stosując sequence na liście ValidationNEL s zbiera wiele błędów, zamiast zatrzymać się na pierwszym:

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

Teraz otrzymujemy:

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

można również użyć sequence na liście Option s, Promise s itd

+2

W rzeczywistości jest bardzo podobny do 'Future.sequence' w strukturze Akka, prawda? –

+0

@FilippoDeLuca: Tak, dokładnie, ten jest po prostu mniej ogólny niż Scalaz. –

+0

Nie jestem jeszcze w stanie w pełni zrozumieć skalase, ale muszę spróbować naprawdę, lub lepiej, musi spróbować mnie :) –

2

zawsze znajdę return oświadczenia nieco niewygodne, ale to następujące prace:

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

'case Left (x) => return Left (x)' może zostać skrócone do 'case l @ Left (_) => return l' –

+2

@KimStebel yes Myślałem, że na początku, ale parametr typu B wynikowy "Albo" jest błędny (wymaga 'Iterable [B]', ale zamiast niego 'B'), więc' Left' jest innym 'Left'. –

+0

ah tak, to prawda –

4

Jeśli nie podoba ci się wyraźny zwrot i chcą wyeliminować dopasowywanie do wzorca podczas skracania kodu trochę, tu jest inny wersja:

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

Podoba mi się, dziękuję. –

Powiązane problemy