2013-07-24 15 views
17

Possiedo un'app che esegue molte chiamate a diversi sistemi di back-end e che spera di utilizzare le incomprensioni per semplificare il flusso di processo attraverso i sistemi di back-end.Combinazione di EitherT e Future

Sto cercando di combinare EitherT (scalaz) e Future (scala 2.10) in modo da poter acquisire il primo errore potenziale (in cui è un problema di sistema futuro o back-end) e restituire un messaggio appropriato all'utente finale. Ho avuto una rapida occhiata a Validation scalaz ma la raccomandazione per catturare il primo errore e non tutti gli errori è usare EitherT.

Sto cercando un semplice esempio in REPL prima, però sto ottenendo il seguente errore

errore: non riusciva a trovare valore implicito per il parametro F: scalaz.Functor [scala.concurrent.Future]

import scala.concurrent._ 
import scalaz._ 
import Scalaz._ 
import ExecutionContext.Implicits.global 

type EitherFuture[+A] = EitherT[Future, String, A] 

def method1Success : EitherFuture[Int] = { 
    println("method 1 success") 
    EitherT { 
    Future { 
     1.right 
    } 
    } 
} 

def method2Failure : EitherFuture[Int] = { 
    println("method 2 failure") 
    EitherT { 
    Future { 
     "fail".left 
    } 
    } 
} 

val m1 = method1Success 

// problem 
m1.isRight 

// problem 
def methodChain1 = { 
    for { 
    a <- method1Success 
    b <- method2Failure 
    } yield b 
} 

Sono ancora nuovo su scala e scalaz quindi qualsiasi suggerimento sarebbe ottimo.

** Aggiornamento **

Includendo scalaz-contrib basata su suggerimento @stew ora ho una versione aggiornata che mostra per-comprensioni con la Combined EitherT e futuro che mostra diverse utilizzare semplici casi di successo backend, un backend fallimento, e di un futuro fallimento

import scala.concurrent._ 
import scalaz._ 
import Scalaz._ 
import ExecutionContext.Implicits.global 
import scalaz.contrib._ 
import scalaz.contrib.std._ 
import scala.concurrent.duration._ 

type EitherFuture[+A] = EitherT[Future, String, A] 

// various methods that mimic success or different failures 
def methodBackendSuccess : EitherFuture[Int] = { 
    println("method backend success") 
    EitherT { 
    Future {1.right} 
    } 
} 

def methodBackendFailure : EitherFuture[Int] = { 
    println("method backend failure") 
    EitherT { 
    Future { "fail".left} 
    } 
} 

def methodFutureFailure : EitherFuture[Int] = { 
    println("method future failure") 
    EitherT { 
    Future.failed(new Exception("future failed")) 
    } 
} 

// different combinations for for-comprehensions 
def methodChainBackendSuccess = { 
    for { 
    a <- methodBackendSuccess 
    b <- methodBackendSuccess 
    c <- methodBackendSuccess 
    } yield c 
} 

def methodChainBackendFailure = { 
    for { 
    a <- methodBackendSuccess 
    b <- methodBackendFailure 
    c <- methodBackendSuccess 
    } yield c 
} 

def methodChainFutureFailure = { 
    for { 
    a <- methodBackendSuccess 
    b <- methodFutureFailure 
    c <- methodBackendSuccess 
    } yield c 
} 

// process results for different chain methods 
def processOutcome(chainMethod: => EitherFuture[Int]):Int = try { 
    val x = Await.result(chainMethod.run, 30 seconds) 
    x.toEither match {        
     case Left(l) => { 
     println("Backend failure <" + l + ">") 
     -1 
     } 
     case Right(r) => { 
     println("Backend success <" + r + ">") 
     r 
     } 
    } 
    } catch { 
    case e: Exception => { 
     println("Future error <" + e.getMessage + ">") 
     -99 
    } 
} 

// run tests 
val backendSuccess = processOutcome(methodChainBackendSuccess) 
val backendFailure = processOutcome(methodChainBackendFailure) 
val futureFailure = processOutcome(methodChainFutureFailure) 
+0

Sei sicuro di aver bisogno del business "oither"? 'Future' può già modellare il fallimento e il sequenziamento ha il comportamento che si desidera. –

+0

Ciao @TravisBrown Mi sto ancora muovendo per la scala, quindi potresti avere quello che mi serve è la capacità di catturare un back-end di successo, un back-end e un futuro fallimento e gestirli diversamente. Ora ho un codice che funziona, aggiornerò la domanda originale e forse questo potrebbe chiarire se ho bisogno di un EitherT e Future combinato o meno. –

+1

Una cosa che mi ha portato un po 'di tempo: Functor [Future] può essere trovato solo se un contesto di esecuzione implicito è nello scope – cvogt

risposta

6

È necessario importare o fornire un'istanza di Functor per Future. Consiglio di utilizzare quello del progetto scalaz-contrib. -contrib è un progetto separato lavorato dalle stesse persone che lavorano su scalaz. Le istanze Future sono in questo pacchetto invece di scalaz-core perché scalaz-core, per ora, mantiene la compatibilità tra scala 2.9 e 2.10.

+0

Aggiungendo '" org.typelevel "%%" scalaz-contrib-210 "%" 0.1.4 "' al mio file Build.scala sono riuscito ad aggiornare il mio esempio per farlo funzionare, aggiornerò la domanda originale con il codice. –

1

Guardate la firma del isRight definita su EitherT:

def isRight(implicit F: Functor[F]): F[Boolean] 

e ' si aspetta un parametro parametrizzato con il parametro type del tuo EitherT, nel tuo caso Future. Scalaz non fornisce un Functor implicita per il tipo di futuro, è necessario scrivere il proprio seguito questo modello:

http://scalaz.github.io/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Functor.scala.html

preavviso tutte le defs implicite per ogni tipo supportato.

+1

Quindi se stavo usando Scalaz 6 sarebbe facile come aggiungere 'implicit def FutureFunctor: Functor [Future] = new Functor [Future] { def fmap [A, B] (t: Future [A], f: A => B): Futuro [B] = t mappa f } 'Sfortunatamente sto lavorando con Scalaz 7 e sembra che le implicite difs si siano spostate. Dove dovrei guardare in Scalaz7 per fare qualcosa di simile? –