我有一串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
}
答案 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.
如果您考虑一下,它很容易阅读,比使用fold
,filter
或模式匹配的其他答案要多得多。 for
表达式也非常容易阅读imho,但它迫使你返回Option[_]
,这不是很自然。