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

Jul*_*n__ 8 functional-programming scala 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
    }
}
Run Code Online (Sandbox Code Playgroud)
// wanted 

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

Sta*_*nko 6

最近我遇到了同样令人讨厌的多个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"))
Run Code Online (Sandbox Code Playgroud)

重构后的代码看起来比多个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"))
Run Code Online (Sandbox Code Playgroud)

这里我们也可以使用普通的函数名,由于偏函数的设计,我们去掉了 else 的部分。唯一的一点是,如果需要添加另一个检查(多一个if-else块),应该在 2 个地方添加:函数声明和作为新的.orElse函数调用

方案三:
很容易注意到,上面所有的偏函数都可以添加到一个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 }
)
Run Code Online (Sandbox Code Playgroud)

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

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

println(result.getOrElse("passed"))
Run Code Online (Sandbox Code Playgroud)

现在如果我们需要再添加一个检查函数,它只能在一个地方完成——列表的另一个元素

选项 4:
我想分享的另一个选项是,也可以通过匿名类覆盖PartialFunction特征并实现其 2 个方法:isDefinedAtapply

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
}
Run Code Online (Sandbox Code Playgroud)

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

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

println(result.getOrElse("passed"))
Run Code Online (Sandbox Code Playgroud)