2015-05-19 4 views
5

Я пишу функцию, которая принимает несколько дополнительных String значений и преобразует каждый из них, чтобы либо Int или Boolean, а затем передают преобразованные значения Unit функций для дальнейшей обработки. Если какое-либо преобразование не выполняется, вся функция должна завершиться ошибкой. Если все преобразования успешны, функция должна обрабатывать преобразованные значения и возвращать успех.преобразование несколько дополнительных значений в Scala

Вот функция, я написал (упрощенно от фактической):

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() 
    } 
} 

Хотя это решение вероятно, будет работать, это очень неудобно. Как я могу улучшить его?

+1

для работающих, потенциально улучшающихся кодов я думаю, лучше место http://codereview.stackexchange.com/ Если это не сработает, не могли бы вы объяснить, в чем проблема, на ваш взгляд? –

+0

@ GáborBakos Это довольно конкретный вопрос о том, что кажется недостающим методом. Я думаю, что здесь хорошо. –

+0

В случае, если это не очевидно, я пишу контроллер для приложения Play. Есть (в настоящее время) 3 необязательных параметра запроса, которые мне нужно обработать. Я предпочел бы сделать их всех одним звонком, так как они связаны. – Ralph

ответ

0

Для завершения я добавляю здесь фрагмент кода, который обрабатывает значения. Однако, если это лучше, чем оригинал спорно. Если вы хотите обработать все значение и собрать результаты преобразования scalaz Validator, это может быть лучшим вариантом.

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) 

В случае необходимости «короткого замыкания» будет работать следующий код.

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

Я не верю, что он сработает, потому что он будет отмечать недостающие (необязательные) значения как ошибку. – Ralph

0

Более императивный стиль может работать, если вы не против этого.

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

Если вы используете scalaz Я хотел бы использовать Option Аппликативные и |@| комбинатор ApplicativeBuilder в. Если какой-либо из входов None, то результатом будет также 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" 
    } 

В чистом Скале, вы можете использовать flatMap по выбору:

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

Я лично предпочитаю аппликативный, потому что становится ясно, что результаты являются независимыми. for-blocks читают мне как «сначала делайте это с помощью a, затем с b, затем с помощью c», тогда как аппликативный стиль больше похож на «со всеми a, b и c, do ...»

Другой вариант с scalaz является sequence, который переворачивает структуру как T[A[X]] в A[T[X]] для проходимой Т и аппликативном А.

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)) 
Смежные вопросы