2015-05-19 12 views
5

Sto scrivendo una funzione che riceve diversi valori opzionali String e converte ciascuno sia ad un Int o Boolean e poi passa i valori convertiti Unit funzioni per l'ulteriore elaborazione. Se una conversione fallisce, l'intera funzione dovrebbe fallire con un errore. Se tutte le conversioni hanno esito positivo, la funzione deve elaborare i valori convertiti e restituire un successo.Conversione valori opzionali multipli in Scala

Qui è la funzione che ho scritto (semplificato dal reale):

f(x: Option[String], y: Option[String], z: Option[String]): Result = { 
    val convertX = x.map(value => Try(value.toInt)) 
    val convertY = y.map(value => Try(value.toBoolean)) 
    val convertZ = z.map(value => Try(value.toBoolean)) 

    val failuresExist = 
    List(convertX, convertY, convertZ).flatten.exists(_.isFailure) 

    if (failuresExist) BadRequest("Cannot convert input") 
    else { 
    convertX.foreach { 
     case Success(value) => processX(value) 
     case _ => 
    } 

    convertY.foreach { 
     case Success(value) => processY(value) 
     case _ => 
    } 

    convertZ.foreach { 
     case Success(value) => processZ(value) 
     case _ => 
    } 

    Ok() 
    } 
} 

Anche se questa soluzione probabilmente funzionerà, è molto imbarazzante. Come posso migliorarlo?

+1

per la lavorazione, i codici potenzialmente migliorabili credo un posto migliore è http://codereview.stackexchange.com/ Nel caso in cui non funzioni, potresti spiegare qual è il problema secondo te? –

+0

@ GáborBakos Questa è una domanda abbastanza specifica su quello che sembra essere un metodo mancante. Penso che vada bene qui. –

+0

Nel caso in cui non sia ovvio, sto scrivendo un controller per un'applicazione Play. Ci sono (attualmente) 3 parametri di query opzionali che devo elaborare. Preferirei farli tutti in un'unica chiamata poiché sono correlati. – Ralph

risposta

0

Per completezza, aggiungo qui un pezzo di codice che elabora i valori richiesti. Comunque se questo è meglio di quello originale è discutibile. Se si desidera elaborare tutto il valore e raccogliere i risultati della trasformazione, scalaz Validator potrebbe essere un'opzione migliore.

import scala.util.Try 

val x = Some("12") 
val y = Some("false") 
val z = Some("hello") 

def process(v: Boolean) = println(s"got a $v") 
def processx(v: Int) = println(s"got a number $v") 

// Abstract the conversion to the appropriate mapping 
def mapper[A, B](v: Option[String])(mapping: String => A)(func: Try[A] => B) = for { 
    cx <- v.map(vv => Try(mapping(vv))) 
    } yield func(cx) 

def f(x: Option[String], y: Option[String], z: Option[String]) = { 
    //partially apply the function here. We will use that method twice. 
    def cx[B] = mapper[Int, B](x)(_.toInt) _ 
    def cy[B] = mapper[Boolean, B](y)(_.toBoolean) _ 
    def cz[B] = mapper[Boolean, B](z)(_.toBoolean) _ 

    //if one of the values is a failure then return the BadRequest, 
    // else process each value and return ok 
    (for { 
    vx <- cx(_.isFailure) 
    vy <- cy(_.isFailure) 
    vz <- cz(_.isFailure) 
    if vx || vy || vz 
    } yield { 
    "BadRequest Cannot convert input" 
    }) getOrElse { 
    cx(_.map(processx)) 
    cy(_.map(process)) 
    cz(_.map(process)) 
    "OK" 
    } 

} 
f(x,y,z) 

Nel caso in cui sia richiesto un comportamento di "cortocircuito", il seguente codice funzionerà.

import scala.util.Try 

val x = Some("12") 
val y = Some("false") 
val z = Some("hello") 


def process(v: Boolean) = println(s"got a $v") 
def processx(v: Int) = println(s"got a number $v") 

def f(x: Option[String], y: Option[String], z: Option[String]) = 
    (for { 
    cx <- x.map(v => Try(v.toInt)) 
    cy <- y.map(v => Try(v.toBoolean)) 
    cz <- z.map(v => Try(v.toBoolean)) 
    } yield { 
    val lst = List(cx, cy, cz) 
     lst.exists(_.isFailure) match { 
     case true => "BadRequest Cannot convert input" 
     case _ => 
      cx.map(processx) 
      cy.map(process) 
      cz.map(process) 
      "OK" 
     } 
    }) getOrElse "Bad Request: missing values" 

f(x,y,z) 
+0

Non credo che funzionerà, perché contrassegnerà i valori mancanti (opzionali) come un errore. – Ralph

0

Uno stile più imperativo potrebbe funzionare, se non ti dispiace.

def f(x: Option[String], y: Option[String], z: Option[String]): Result = { 
    try { 
     val convertX = x.map(_.toInt) 
     val convertY = y.map(_.toBoolean) 
     val convertZ = z.map(_.toBoolean) 
     convertX.foreach(processX) 
     convertY.foreach(processY) 
     convertZ.foreach(processZ) 
     Ok() 
    } catch { 
     case _: IllegalArgumentException | _: NumberFormatException => BadRequest("Cannot convert input") 
    } 
} 
0

Se stai usando scalaz Vorrei utilizzare l'applicativa Opzione e |@| combinatore di ApplicativeBuilder. Se uno qualsiasi degli ingressi è None, il risultato è anche None.

import scalaz.std.option.optionInstance 
import scalaz.syntax.apply._ 
val result: Option[String] = 
    Some(1) |@| Some("a") |@| Some(true) apply { 
    (int, str, bool) => 
     s"int is $int, str is $str, bool is $bool" 
    } 

In puro Scala, è possibile utilizzare flatMap in opzione:

val result: Option[String] = 
    for { 
    a <- aOpt 
    b <- bOpt 
    c <- cOpt 
    } yield s"$a $b $c" 

Io personalmente preferisco l'applicativo perché rende chiaro che i risultati sono indipendenti. per i blocchi leggimi come "prima fai questo con a, poi questo con b, poi questo con c", mentre lo stile applicativo è più simile a "b, and c, do ..."

un'altra opzione con scalaz è sequence, che inverte una struttura come T[A[X]] in A[T[X]] per T traversable ed applicative A.

import scalaz.std.option.optionInstance 
import scalaz.std.list.listInstance 
import scalaz.syntax.traverse._ 
val list: List[Option[Int]] = List(Option(1), Option(4), Option(5)) 
val result: Option[List[Int]] = list.sequence 
// Some(List(1, 4, 5))