2013-07-24 13 views
17

Mam aplikację, która wykonuje wiele wywołań do różnych systemów zaplecza i ma nadzieję użyć do zrozumienia, aby uprościć przepływ procesów w systemach zaplecza.Łączenie EitherT i Future

Szukam połączenia EitherT (skalaz) i Future (scala 2.10), dzięki czemu mogę uchwycić pierwszy potencjalny błąd (gdzie jest to problem przyszłości lub systemu zaplecza) i zwrócić odpowiednią wiadomość do użytkownika końcowego. Szybko sprawdziłem poprawność, ale zalecenie przechwytywania pierwszego błędu, a nie wszystkich błędów, polega na użyciu EitherT.

Próbuję prosty przykład w REPL pierwszy jednak Dostaję następujący błąd

błąd: nie można odnaleźć ukrytą wartość parametru 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 
} 

Wciąż jestem nowy zarówno dla scala, jak i scalaza, więc wszelkie wskazówki będą świetne.

** Aktualizacja **

Włączając scalaz-contrib na podstawie @stew sugestię Mam teraz zaktualizowaną wersję, która pokazuje na-listowe o połączonej EitherT i przyszłość pokazując różne przypadki proste użycie backend sukces, backend awaria, a przyszłość awaria

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

Czy jesteś pewny, że potrzebujesz w ogóle "interesu"? 'Future' może już modelować awarię, a sekwencjonowanie ma pożądane działanie. –

+0

Hi @TravisBrown Wciąż dostaję głowę wokół scala, więc możesz mieć rację, czego potrzebuję, to zdolność przechwytywania sukcesu backendu, niepowodzenie backendu i przyszłej awarii i radzenia sobie z nimi inaczej. Mam teraz kod, który działa, będę aktualizować oryginalne pytanie i być może to może wyjaśnić, czy potrzebuję połączenia EitherT i Future, czy też nie. –

+1

Jedna rzecz, która zabrała mi trochę czasu: Functor [Future] można znaleźć tylko, jeśli niejawny executioncontext znajduje się w zakresie – cvogt

Odpowiedz

6

Musisz zaimportować lub dostarczyć instancję Functor dla przyszłości. Polecam używanie tego z projektu scalaz-contrib. -contrib to osobny projekt opracowany przez tych samych ludzi, którzy pracują nad skalą. Przyszłe instancje znajdują się w tym pakiecie, a nie w jądrze typu "scalaz", ponieważ rdzeń typu "scalaz" zachowuje na razie kompatybilność między scala 2.9 a 2.10.

+0

Dodanie "org.typelevel" %% "scalaz-contrib-210"% "0.1.4" 'do mojego pliku Build.scala Udało mi się zaktualizować mój przykład, aby działało, zaktualizuję oryginalne pytanie kod. –

1

Spójrz na podpis isRight określonej na EitherT:

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

It spodziewa się parametryzacji Funktora za pomocą parametru typu twojego EitherT, w twoim przypadku Future. Scalaz nie zapewnia niejawna funktor dla typu Future, trzeba napisać własną podstawie tego modelu:

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

uwaga Wszystkie domniemane defs dla każdego obsługiwanego typu.

+1

Więc jeśli używałem Scalaz 6, byłoby to tak proste, jak dodanie 'niejawnego def FutureFunctor: Functor [Future] = new Functor [Future] { def fmap [A, B] (t: Future [A], f: A => B): Przyszłość [B] = t mapa f } 'Niestety pracuję ze Scalazem 7 i wydaje się, że implicite defs przesunęły się. Gdzie powinienem szukać w Scalaz7, żeby zrobić coś podobnego? –