2016-02-09 1 views
1

Il existe des méthodes map/flatMap, il existe également des méthodes recover/recoverWith dans l'API standard Scala Future. Pourquoi il n'y a pas collectWith?Qu'en est-il d'une méthode Scala Future # collectWith?

Le code de la méthode collect est assez simple:

def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = 
    map { 
    r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t)) 
    } 

Le code de la méthode collectWith est alors facile d'imaginer:

def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] = 
    flatMap { 
    r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t)) 
    } 

Je sais que je peux le mettre en œuvre et « étendre "l'API standard Future facilement grâce à cet article: http://debasishg.blogspot.fr/2008/02/why-i-like-scalas-lexically-scoped-open.html

Je l'ai fait dans mon projet:

class RichFuture[T](future: Future[T]) { 
    def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] = 
    future.flatMap { 
     r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t)) 
    } 
} 

trait WithRichFuture { 
    implicit def enrichFuture[T](person: Future[T]): RichFuture[T] = new RichFuture(person) 
} 

Peut-être que mes besoins pour cela ne justifient pas de l'implémenter dans l'API standard?

Voici pourquoi j'ai besoin de cette méthode dans mon projet Play2:

def createCar(key: String, eligibleCars: List[Car]): Future[Car] = { 
    def handleResponse: PartialFunction[WSResponse, Future[Car]] = { 
    case response: WSResponse if response.status == Status.CREATED => Future.successful(response.json.as[Car]) 
    case response: WSResponse 
     if response.status == Status.BAD_REQUEST && response.json.as[Error].error == "not_the_good_one" => 
      createCar(key, eligibleCars.tail) 
    } 

    // The "carApiClient.createCar" method just returns the result of the WS API call. 
    carApiClient.createCar(key, eligibleCars.head).collectWith(handleResponse) 
} 

Je ne sais pas comment faire sans ma méthode collectWith.

Peut-être que ce n'est pas la bonne façon de faire quelque chose comme ça?
Connaissez-vous un meilleur moyen?


EDIT:

je peut-être une meilleure solution pour la méthode createCar qui ne nécessite pas la méthode collectWith:

def createCar(key: String, eligibleCars: List[Car]): Future[Car] = { 
    for { 
    mayCar: Option[Car] <- Future.successful(eligibleCars.headOption) 
    r: WSResponse <- carApiClient.createCar(key, mayCar.get) if mayCar.nonEmpty 
    createdCar: Car <- Future.successful(r.json.as[Car]) if r.status == Status.CREATED 
    createdCar: Car <- createCar(key, eligibleCars.tail) if r.status == Status.BAD_REQUEST && r.json.as[Error].error == "not_the_good_one" 
    } yield createdCar 
} 

Que pensez-vous de cette deuxième solution?


Deuxième édition:

Pour information, voici ma solution finale grâce à réponse @Dylan:

def createCar(key: String, eligibleCars: List[Car]): Future[Car] = { 

    def doCall(head: Car, tail: List[Car]): Future[Car] = { 
    carApiClient 
     .createCar(key, head) 
     .flatMap(response => 
     response.status match { 
      case Status.CREATED => Future.successful(response.json.as[Car]) 
      case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" => 
      createCar(key, tail) 
     } 
    ) 
    } 

    eligibleCars match { 
    case head :: tail => doCall(head, tail) 
    case Nil => Future.failed(new RuntimeException) 
    } 

} 

Jules

Répondre

1

Que diriez-vous:

def createCar(key: String, eligibleCars: List[Car]): Future[Car] = { 
    def handleResponse(response: WSResponse): Future[Car] = response.status match { 
    case Status.Created => 
     Future.successful(response.json.as[Car]) 
    case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" => 
     createCar(key, eligibleCars.tail) 
    case _ => 
     // just fallback to a failed future rather than having a `collectWith` 
     Future.failed(new NoSuchElementException("your error here")) 
    } 

    // using flatMap since `handleResponse` is now a regular function 
    carApiClient.createCar(key, eligibleCars.head).flatMap(handleResponse) 
} 

Deux changements:

  • handleResponse n'est plus une fonction partielle. Le case _ renvoie un échec avenir, ce qui est essentiellement ce que vous faisiez dans votre implémentation personnalisée collectWith.
  • utilisation flatMap au lieu de collectWith, puisque handleResponse convient maintenant que la signature de la méthode

modifier pour info

Si vous avez vraiment besoin de soutenir l'approche PartialFunction, vous pouvez toujours convertir une PartialFunction[A, Future[B]] à Function[A, Future[B]] en appelant orElse sur la fonction partielle, p.ex.

val handleResponsePF: PartialFunction[WSResponse, Future[Car]] = { 
    case .... 
} 

val handleResponse: Function[WSResponse, Future[Car]] = handleResponsePF orElse { 
    case _ => Future.failed(new NoSucheElementException("default case")) 
} 

Cela vous permettra d'adapter une fonction partielle existante pour entrer dans un appel flatMap.

(d'accord, sur le plan technique, il le fait déjà, mais vous seriez lancer MatchErrors plutôt que vos propres exceptions personnalisées)

+0

Je trouve votre solution flatMap assez bien parce que lisible! Merci =) Et merci pour les informations sur la traduction d'une fonction partielle en fonction. Je ne connaissais pas cette astuce =) –