在Scala中转换多个可选值

时间:2015-05-19 12:18:56

标签: scala type-conversion optional

我正在编写一个函数,它接收几个可选的String值并将每个值转换为IntBoolean,然后将转换后的值传递给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()
  }
}

虽然这个解决方案可能会起作用,但它非常尴尬。我该如何改进呢?

3 个答案:

答案 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))