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)
最近我遇到了同样令人讨厌的多个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 个方法:isDefinedAt和apply
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)