2014-04-24 15 views
11

Diciamo che hai un sacco di metodi:Usando per-la comprensione, provare e sequenze in Scala

def foo() : Try[Seq[String]] 
def bar(s:String) : Try[String] 

e si vuole fare una for-comprhension:

for { 
    list <- foo 
    item <- list 
    result <- bar(item) 
} yield result 

naturalmente questo non verrà compilato poiché Seq non può essere usato con Try in questo contesto.

Chiunque ha una buona soluzione su come scrivere questo pulito senza romperlo in due separati per?

Mi sono imbattuto in questo problema di sintassi per la terza volta e ho pensato che fosse giunto il momento di chiedere questo.

+0

Vedere anche http://stackoverflow.com/q/4719592/298389 –

risposta

3

IMHO: Prova e Seq è più di quello che è necessario definire un trasformatore monade:

Codice per la libreria:

case class trySeq[R](run : Try[Seq[R]]) { 
    def map[B](f : R => B): trySeq[B] = trySeq(run map { _ map f }) 
    def flatMap[B](f : R => trySeq[B]): trySeq[B] = trySeq { 
    run match { 
     case Success(s) => sequence(s map f map { _.run }).map { _.flatten } 
     case Failure(e) => Failure(e) 
    } 
    } 

    def sequence[R](seq : Seq[Try[R]]): Try[Seq[R]] = { 
    seq match { 
     case Success(h) :: tail => 
     tail.foldLeft(Try(h :: Nil)) { 
      case (Success(acc), Success(elem)) => Success(elem :: acc) 
      case (e : Failure[R], _) => e 
      case (_, Failure(e)) => Failure(e) 
     } 
     case Failure(e) :: _ => Failure(e) 
     case Nil => Try { Nil } 
    } 
    } 
} 

object trySeq { 
    def withTry[R](run : Seq[R]): trySeq[R] = new trySeq(Try { run }) 
    def withSeq[R](run : Try[R]): trySeq[R] = new trySeq(run map (_ :: Nil)) 

    implicit def toTrySeqT[R](run : Try[Seq[R]]) = trySeq(run) 
    implicit def fromTrySeqT[R](trySeqT : trySeq[R]) = trySeqT.run 
} 

e dopo si può usare per-comrehension (solo importare la libreria):

def foo : Try[Seq[String]] = Try { List("hello", "world") } 
def bar(s : String) : Try[String] = Try { s + "! " } 

val x = for { 
    item1 <- trySeq { foo } 
    item2 <- trySeq { foo } 
    result <- trySeq.withSeq { bar(item2) } 
} yield item1 + result 

println(x.run) 

e funziona per:

def foo() = Try { List("hello", throw new IllegalArgumentException()) } 
// x = Failure(java.lang.IllegalArgumentException) 
+0

Ho avuto un sentimento nelle mie viscere che ha a che fare con una sorta di magia monade. Ho bisogno di passare un po 'di tempo ad imparare questa roba. Sembra davvero utile. – almendar

2

Una Prova può essere convertita in un'opzione, che è possibile utilizzare in una comprensione. Per esempio.

scala> def testIt() = { 
    | val dividend = Try(Console.readLine("Enter an Int that you'd like to divide:\n").toInt) 
    | dividend.toOption 
    | } 
testIt:()Option[Int] 

scala> for (x <- testIt()) println (x * x) 
Enter an Int that you'd like to divide: 

scala> for (x <- testIt()) println (x * x) 
Enter an Int that you'd like to divide: 
1522756 

prima volta che sono entrato "w", poi la seconda volta 1234.

+0

Prova può essere usato in una comprensione preliminare. Ma non può essere mescolato con SeqLike.Il problema è che ci sarà una corrispondenza di tipo mancante dal momento che avrà bisogno di provare [...] e troverà SeqLike. – almendar

+0

"warning: metodo readLine nella classe DeprecatedConsole è obsoleto: utilizzare il metodo in scala.io.StdIn" –

3

Si può approfittare del fatto che Try possono essere convertiti in Option, e Option a Seq:

for { 
    list <- foo.toOption.toSeq // toSeq needed here, as otherwise Option.flatMap will be used, rather than Seq.flatMap 
    item <- list 
    result <- bar(item).toOption // toSeq not needed here (but allowed), as it is implicitly converted 
} yield result 

Questo restituirà un (eventualmente vuoto, se il Try non è riuscito) Seq.

Se si desidera mantenere tutti i dettagli delle eccezioni, è necessario un Try[Seq[Try[String]]]. Questo non può essere fatto con un singolo per la comprensione, così si sta meglio attaccare con pianura map:

foo map {_ map bar} 

Se si vuole mescolarsi tuoi Try s e Seq s in modo diverso, le cose si fanno fiddlier, come non esiste un modo naturale per appiattire uno Try[Seq[Try[String]]]. @ La risposta di Yury dimostra il genere di cose che dovresti fare.

Oppure, se siete interessati solo gli effetti collaterali del codice, si può solo fare:

for { 
    list <- foo 
    item <- list 
    result <- bar(item) 
} result 

Questo funziona perché foreach ha una firma di tipo meno restrittivo.

+0

Il problema è che perderò i dettagli dell'eccezione. Quello che vorrei ottenere è elaborare tutti gli elementi dal Seq e fermarsi se qualcuno getterà un'eccezione. Oltre a ciò, grazie per l'idea con Option. Un bel trucco – almendar

+1

Ho aggiornato con una breve discussione sulle opzioni che mantengono i dettagli delle eccezioni. –