2016-11-15 71 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[_],這不是很自然。