我正在编写一个函数,它接收几个可选的String
值并将每个值转换为Int
或Boolean
,然后将转换后的值传递给Unit
函数进一步处理。如果任何转换失败,整个函数将失败并显示错误。如果所有转换都成功,则该函数应处理转换后的值并返回成功。
这是我写的函数(从实际中简化):
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()
}
}
虽然这个解决方案可能会起作用,但它非常尴尬。我该如何改进呢?
答案 0 :(得分: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)
在a&#34;短路&#34;行为是必需的,以下代码将起作用。
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)
答案 1 :(得分: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")
}
}
答案 2 :(得分:0)
如果您使用的是scalaz,我会使用Option applicative和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"
}
在纯scala中,您可以在选项上使用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做所有,做......”
scalaz的另一个选项是sequence
,它将T[A[X]]
之类的结构反转为A[T[X]]
,用于可遍历的T和应用程序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))