如何在Scala中重构(if / elsif / elsif)链?

时间:2016-11-15 19:50:51

标签: scala functional-programming higher-order-functions

我有一串if / else if语句,不能自我解释。我想将每个函数提取到具有明确解释名称的自己的函数中,然后将这些函数链接起来。

如何在scala中途停止调用链?

这是一个代码示例:

// actual code 

for( klass <- program.classes ) {
    if ( complicated boolean ) { //checkVars
        error1
    } else if ( complicated boolean ) { //checkMethods
        error2
    } else if ( ... ) { //...
        error3
    } else {
        complicated good case code
    }
}
// wanted 

for( klass <- program.classes ) {
    (checkName 
     andThen checkVars
     andThen checkMethods
     andThen addToContext) (klass)
// where the chaining stops if a check fails
}

8 个答案:

答案 0 :(得分:3)

您可以使用在Option理解中返回Option[_]的{​​{1}}类型和方法进行链验证,同时提取部分结果。当选项返回无

时,处理停止
for

答案 1 :(得分:3)

最近,我有一个令人讨厌的多个 if-else 块,看起来很糟糕
我想出了下一个选择:

选项1:
最简单的方法是为每个 if-else 块引入一个单独的函数,对于示例条件,我只是将整数常量与文字进行比较,但是您可以将其替换为其他任何东西

val x = 3

def check1: Option[String] = {
  if (x == 1) Some("error 1") else None
}

def check2: Option[String] = {
  if (x == 2) Some("error 2") else None
}

def check3: Option[String] = {
  if (x == 3) Some("error 3") else None
}

// we can chain Option results together
// using "orElse" function
val result = check1
  .orElse(check2)
  .orElse(check3)

// result contains a returned value from one
// of the above functions,
// or if no checks worked, it ends up with "Option.None"
println(result.getOrElse("passed"))

重构后的代码看起来比多个 if-else 语句要好得多,现在我们可以为每个函数指定一个合理的名称,就我而言,它消除了样式检查器中的圈复杂度警告

选项2:
第一种方法仍然具有“ else”部分,我想不惜一切代价摆脱它,所以我使用了部分函数

// just an alias, so I don't need to write
// the full parameter type for every function
type Validator = PartialFunction[Int, Option[String]]

def check1: Validator = { case x if x == 1 => Some("error 1") }
def check2: Validator = { case x if x == 2 => Some("error 2") }
def check3: Validator = { case x if x == 3 => Some("error 3") }
def default: Validator = { case _ => None }

// we can chain together partial functions
// the same way as we did with Option type
val result = check1
  .orElse(check2)
  .orElse(check3)
  .orElse(default) {
    3 // this is an actual parameter for each defined function
  }

// the result is Option
// if there was an error we get Some(error)
// otherwise the result is Option.None in which case
// we return "passed"
println(result.getOrElse("passed"))

在这里我们也可以使用普通的函数名,而由于部分函数的设计,我们摆脱了其他部分。唯一的事情是,如果需要添加另一个检查(再添加一个 if-else 块),则应在两个位置添加它:函数声明和作为新的 .orElse < / em>函数调用

选项3:
容易注意到,以上所有部分功能都可以添加到 List

type Validator = PartialFunction[Int, Option[String]]

val validations: List[Validator] = List(
  { case x if x == 1 => Some("error 1") },
  { case x if x == 2 => Some("error 2") },
  { case x if x == 3 => Some("error 3") },
  { case _ => None }
)

然后可以遍历 List 并可以在遍历期间应用 .orElse 函数。应该以任何方式完成,我选择了 foldLeft 函数

val result = validations.tail.foldLeft(validations.head)(_.orElse(_)) {
  3
}

println(result.getOrElse("passed"))

现在,如果我们需要再添加一个检查功能,则只能在一个位置- List

的另一个元素上完成

选项4:
我想分享的另一个选择是,还可以通过匿名类覆盖 PartialFunction 特性并实现其2种方法: isDefinedAt apply < / p>

type Validator = PartialFunction[Int, Option[String]]

val check1 = new Validator {
  override def isDefinedAt(x: Int): Boolean = x == 1
  override def apply(v1: Int): Option[String] = Some("error 1")
}

val check2 = new Validator {
  override def isDefinedAt(x: Int): Boolean = x == 2
  override def apply(v1: Int): Option[String] = Some("error 2")
}

val check3 = new Validator {
  override def isDefinedAt(x: Int): Boolean = x == 3
  override def apply(v1: Int): Option[String] = Some("error 3")
}

val default = new Validator {
  override def isDefinedAt(x: Int): Boolean = true
  override def apply(v1: Int): Option[String] = None
}

然后,我们可以像在第二个选项中那样链接那些函数

val result = check1
  .orElse(check2)
  .orElse(check3)
  .orElse(default) {
    3
  }

println(result.getOrElse("passed"))

答案 2 :(得分:2)

这很大程度上取决于您希望在错误上发生什么,但对于使用选项的链式地图来说这似乎是个好例子:

def checkName(klass: Klass): Option[Klass] = if (compBoolean) Some(klass) else None
def checkVars(klass: Klass): Option[Klass] = if (compBoolean) Some(klass) else None
def checkMethods(klass: Klass): Option[Klass] = if (compBoolean) Some(klass) else None
def finalOp(klass: Klass): OutputClass = //your final operation

// Use the above checks
program.classes.map(checkName(_).flatMap(checkVars).flatMap(checkMethods).map(finalOp).getOrElse(defaultResult))

如果您想跳过/省略未通过所有检查的元素,那么您将使用flatMap

program.classes.flatMap(checkName(_).flatMap(checkVars).flatMap(checkMethods).map(finalOp))

答案 3 :(得分:2)

使用带过滤器的选项可以让检查只返回一个布尔值。例如

def checkName(klass: Klass): Boolean = ???
def checkVars(klass: Klass): Boolean = ???
def checkMethods(klass: Klass): Boolean = ???
def finalOp(klass: Klass): OutputClass = ???

Option(klass)
  .filter(checkName)
  .filter(checkVars)
  .filter(checkMethods)
  .map(finalOp)

如果所有检查都通过,您将留下Some()None如果其中任何一项检查失败。

答案 4 :(得分:2)

program.classes foreach {
  case klass if checkName(klass) => error1
  case klass if checkVars(klass) => error2
  case klass if checkMethods(klass) => error3
  case klass => addToContext(klass)
}

答案 5 :(得分:2)

要使用部分功能组合回答问题(如问题所示),我们将每个检查定义为PartialFunction。我们还使用Try作为结果类型。然后,Try可以保留处理期间可能出现的特定错误信息。 Option,这似乎是一个受欢迎的选择,并没有保留无法找到元素的原因。我不会用它来实现检查,除非我们真的不关心任何错误信息。 )

简化示例:

import scala.util.{Try, Success, Failure}

val check1:PartialFunction[Int, Try[String]] = {case x if x==1 => Failure(new Exception("error1"))}
val check2:PartialFunction[Int, Try[String]] = {case x if x==2 => Failure(new Exception("error2"))}
val check3:PartialFunction[Int, Try[String]] = {case x if x==3 => Failure(new Exception("error3"))}
val process: PartialFunction[Int, Try[String]] = {case x => Success(s"[$x] processed OK")}

val checks = check1 orElse check2 orElse check3 orElse process

for (i <- 1 to 4) yield (checks(i))
//  scala.collection.immutable.IndexedSeq[scala.util.Try[String]] = Vector(
//    Failure(java.lang.Exception: error1),
//    Failure(java.lang.Exception: error2),
//    Failure(java.lang.Exception: error3),
//    Success([4] processed OK)
//)

答案 6 :(得分:0)

使用选项折叠链

您可以使用Option s fold方法。

fold在标准库中定义如下

final def fold[B](ifEmpty: => B)(f: Int => B): B

这可以应用于任何一般用例。您所要做的就是不断地返回选项。如果在下面的情况下任何方法返回None,则链断裂。在下面的代码中,您通过发送Some或None作为消息来与下一个操作进行通信。

def f1: Option[_] = ???
def f2: Option[_] = ???
def f3: Option[_] = ???

f1.fold[Option[Unit]](None)(_ => f2).fold[Option[Unit]](None)(_ => f3)

Scala REPL

scala> Option(1).fold[Option[Unit]](None)(_ => Some(println("hello"))).fold[Option[Unit]](None)(_ => Some(println("scala")))
hello
scala
res59: Option[Unit] = Some(())

scala> None.fold[Option[Unit]](None)(_ => Some(println("hello"))).fold[Option[Unit]](None)(_ => Some(println("scala")))
res60: Option[Unit] = None

scala> Option(1).fold[Option[Unit]](None)(_ => None).fold[Option[Unit]](None)(_ => Some(println("scala")))
res61: Option[Unit] = None

答案 7 :(得分:0)

这是我在C中经常使用的模式,也适用于Scala。我今天早上记得它。如果要将&&重命名为thenIfSuccess或类似名称(未显示),则可以创建隐式方法。

它充分利用了&&在其第二个参数中是懒惰的事实。

def checkName(klass: Klass): Boolean = ???
def checkVars(klass: Klass): Boolean = ???
def checkMethods(klass: Klass): Boolean = ???
def finalOp(klass: Klass): Boolean = ???
// just chain the method calls :
checkName(cls) && checkVars(cls) && checkMethods(cls) && finalOp(cls)


// this will call each method in order and stop if one fails.

如果您考虑一下,它很容易阅读,比使用foldfilter或模式匹配的其他答案要多得多。 for表达式也非常容易阅读imho,但它迫使你返回Option[_],这不是很自然。