如何在 Scala 中重构 (if / elsif / elsif) 链?
How to refactor (if / elsif / elsif) chain in Scala?
我有一连串的 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
}
您可以在 for
理解中使用 Option
类型和 return Option[_]
的方法来链式验证,同时提取部分结果。当一个选项 returns None
时处理停止
for {
klass <- program.classes
name <- checkName // Option[String]
vars <- checkVars // Option[SomeType]
methods <- checkMethods // Option[SomeOtherT]
ctx <- addToContext // Option[...]
} {
// do something with klass
// if you got here, all the previous Options returned Some(_)
}
这在很大程度上取决于您希望在错误时发生什么,但这似乎是使用选项的链式映射的好案例:
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))
如果您想要 skip/omit 个未通过所有检查的元素,那么您可以使用 flatMap
:
program.classes.flatMap(checkName(_).flatMap(checkVars).flatMap(checkMethods).map(finalOp))
将 Option 与过滤器一起使用将使您可以简单地进行检查 return 一个布尔值。例如
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
。
使用 Option 的折叠链
您可以使用 Option
s fold
方法。
fold 在标准库中是这样定义的
final def fold[B](ifEmpty: => B)(f: Int => B): B
这可以应用于任何一般用例。您所要做的就是 return 不断地选择某事。在下面的情况下,如果任何方法 returns 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
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)
}
为了使用偏函数组合回答问题(如问题所问),我们将每个检查定义为 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)
//)
这是我在 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.
如果你仔细想想,它非常容易阅读,比使用 fold
、filter
或模式匹配的其他答案要多得多。 for
表达式也很容易阅读恕我直言,但它迫使你 return Option[_]
这不是很自然。
最近我遇到了同样烦人的多个 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"))
这里我们也可以使用普通的函数名,并且由于部分函数的设计,我们去掉了else的部分。唯一的问题是,如果需要添加另一个检查(再添加一个 if-else 块),则应在 2 个位置添加:函数声明和作为新的 .orElse 函数调用
选项 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:
我想分享的另一个选择是,也可以通过匿名 class 覆盖 PartialFunction 特征并实现它的 2 个方法:isDefinedAt并且 申请
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"))
我有一连串的 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
}
您可以在 for
理解中使用 Option
类型和 return Option[_]
的方法来链式验证,同时提取部分结果。当一个选项 returns None
for {
klass <- program.classes
name <- checkName // Option[String]
vars <- checkVars // Option[SomeType]
methods <- checkMethods // Option[SomeOtherT]
ctx <- addToContext // Option[...]
} {
// do something with klass
// if you got here, all the previous Options returned Some(_)
}
这在很大程度上取决于您希望在错误时发生什么,但这似乎是使用选项的链式映射的好案例:
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))
如果您想要 skip/omit 个未通过所有检查的元素,那么您可以使用 flatMap
:
program.classes.flatMap(checkName(_).flatMap(checkVars).flatMap(checkMethods).map(finalOp))
将 Option 与过滤器一起使用将使您可以简单地进行检查 return 一个布尔值。例如
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
。
使用 Option 的折叠链
您可以使用 Option
s fold
方法。
fold 在标准库中是这样定义的
final def fold[B](ifEmpty: => B)(f: Int => B): B
这可以应用于任何一般用例。您所要做的就是 return 不断地选择某事。在下面的情况下,如果任何方法 returns 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
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)
}
为了使用偏函数组合回答问题(如问题所问),我们将每个检查定义为 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)
//)
这是我在 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.
如果你仔细想想,它非常容易阅读,比使用 fold
、filter
或模式匹配的其他答案要多得多。 for
表达式也很容易阅读恕我直言,但它迫使你 return Option[_]
这不是很自然。
最近我遇到了同样烦人的多个 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"))
这里我们也可以使用普通的函数名,并且由于部分函数的设计,我们去掉了else的部分。唯一的问题是,如果需要添加另一个检查(再添加一个 if-else 块),则应在 2 个位置添加:函数声明和作为新的 .orElse 函数调用
选项 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:
我想分享的另一个选择是,也可以通过匿名 class 覆盖 PartialFunction 特征并实现它的 2 个方法:isDefinedAt并且 申请
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"))