2016-11-15 72 views
4

我有一个链条if/else if声明不是自我解释。我想用清晰的解释性名称将它们分解为它自己的函数,然后链接这些函数。如何在Scala中重构(if/elsif/elsif)链?

如何在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

错误情况下期望的操作是什么?想要抛出一个异常,打印一些东西并继续前进,输出一个错误对象? –

+0

打印东西 –

回答

3

可以使用Option类型和在for理解返回Option[_]到链验证,同时提取部分结果的方法。

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)) 

如果你想:当一个选项,返回无

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(_) 
} 
+0

谢谢你的回答。我考虑过这个问题,但这让我很烦恼,因为检查不应该返回任何东西。如果他们返回一个虚拟值,我不确定代码是否容易理解。 +1这个好主意虽然 –

+1

也许我偏爱自己的解决方案:-)但是在Scala中,大多数事情都会返回一些东西,因为如果它们不这样做,它们会以副作用的方式运行,那就违背了“The Commandments”( “最佳做法”)。 – radumanolescu

+2

如果你不喜欢提取一个值,你可能会提取......一个“单元”。事实上,'Option [Unit]'有两个可能的值'Some(())'和'None',就像'Boolean'一样,理解允许你在继续检查其他属性的同时返回'Some(() )'。请注意,由于您不关心该值(它始终是'()'),所以您可以将'_'放在''for'块中每个<-'的lhs中。 –

2

这在很大程度上取决于你想上的错误发生什么,但这种使用的选项似乎是一个很好的案例链接的地图处理停止跳过/忽略其失败的所有检查的元素,那么你可以使用一个flatMap:带过滤

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

我感到恼火的事实,检查返回一个值,但最终链接看起来不错。 +1代码示例'checkName'等等......但是真的没有别的选择,只能从检查中返回一些东西吗? –

+1

是的,我在想和我写的一样的东西,if(compBoolean)Some(klass)else None'非常难看,并且不太习惯。 –

2

使用选项将允许你有检查,只是返回AB oolean值。例如

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如果其中任何失败。

+0

定义的谓词函数只是为了便于阅读,您可以将过滤器中的复杂条件直接作为lambda函数调用。 –

+0

这比我的回答更有意义 –

+0

的确如此。我喜欢从检查中返回“布尔”的想法。这是有道理的。 +1 –

0

链使用期权的倍

您可以使用Option小号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 
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) 
} 
+1

你的回答太简单了。请给出一些解释,为什么应该这样做 –

+0

聪明......虽然这个有趣的想法可能太聪明了^^ +1。 –

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) 
//) 
+0

非常有趣(+1),但我需要链接成功,而不是失败 –

+0

@Julien__这种方法“成功”并在失败时存在。请注意过程如何通过每次检查,直到它失败(数字1-3)或进入最终的'process'语句(#4)。 – maasg

+0

哦,我现在看到。这是liljerk答案的“部分功能”版本。我发现这很令人不安,因为'orElse'通常与失败案例相关联。但是这里的部​​分功能并没有在成功的情况下被定义。有一个间接层。 (这有点像说“不是假”而不是“真”)。 –

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[_],这不是很自然。