Convert multiple optional values ​​to Scala

I am writing a function that takes a few optional String values ​​and converts each one to Int or Boolean , and then passes the converted values ​​to Unit functions for further processing. If any conversion is not performed, the entire function should fail. If all conversions are successful, the function must process the converted values ​​and return success.

Here is the function I wrote (simplified from the actual):

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

Although this solution is likely to work, it is very inconvenient. How can I improve it?

+5
source share
3 answers

To finish, I add a piece of code here that processes the values. However, if it is better than the original is debatable. If you want to process the entire value and collect the results of the scalaz Validator conversion, this might be the best option.

 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) 

If necessary, a "short circuit" will work the following code.

 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
source

A more persistent style might work if you don't mind.

 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
source

If you are using scalaz, I would use a combination of Option and ApplicativeBuilder |@| . If any of the inputs is None , then the result is also 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 pure scala, you can use flatMap as an option:

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

I personally prefer the application because it is clear that the results are independent. for-blocks read to me like "first do it with a, then with b, then with c", while the applicative style is more like "with all a, b and c, do ..."

Another option with a tale is a sequence that inverts a structure like T[A[X]] into A[T[X]] for traced T and 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)) 
0
source

All Articles