2010-08-20 24 views
8

對於電梯開發,我有時需要使用match - case聲明如下。 (爲了便於理解,重寫爲普通的scala。)給他們一個註釋:這些實際上是不同的部分函數,​​它們在代碼的不同部分中定義,所以重要的是case語句在guard或guard之前失敗,以便讓其他部分函數評估(如果匹配失敗,那就是)。現在scala內部變量的範圍case case guard

// The incoming request 
case class Req(path: List[String], requestType: Int) 

// Does some heavy database action (not shown here) 
def findInDb(req: Req):Option[Int] = 
    if(req.path.length > 3) Some(2) else None 

Req("a"::"b"::Nil, 3) match { 
    case [email protected](`path` :: _ :: Nil, 3) if findInDb(r).isDefined => 
    doSomethingWith(findInDb(r)) 
    case [email protected](`path` :: _ :: Nil, _) => doDefault 
    case _ => doNothing 
} 

,爲了知道case語句成功,我必須findInDb查詢數據庫,並檢查結果是否有效。之後,我必須再次調用它才能使用該值。

做這樣的事情

case [email protected](path, 3) if {val res = findInDb(r); res.isDefined} => 

不起作用,因爲res範圍則僅限於括號內。

我當然可以在外面定義一個var res = _並分配給它,但是我不這麼做。

是否可以通過任何方式在警衛內聲明變量?如果有可能做到case [email protected](…)爲什麼不是case [email protected]() if [email protected](r.isDefined)

回答

0

你試過case r @ Req() if [email protected](r.isDefined)

scala> val t3 =(1, "one", 1.0) 
t3: (Int, java.lang.String, Double) = (1,one,1.0) 

scala> t3 match { case t @ (1, s, d) if t._3 < 2.0 => println("OK"); case _ => println("No-Go") } 
OK 
+0

我不太明白你的答案。正如我寫的(或者至少希望人們會相信我真的試過它),我試着用'if res @(something)',它不是有效的語法。 – Debilski 2010-08-20 16:16:01

9

你其實非常接近。缺失的關鍵是使用提取器而不是警戒表達式。

object FindInDb{ 
    def unapply(r:Req):Option[Int]= findInDb(r)  
} 

Req("a"::"b"::Nil, 3) match { 
    case [email protected](Req(`path` :: _ :: Nil, 3))=> doSomethingWith(dbResult) 
    case Req(`path` :: _ :: Nil, _) => doDefault 
    case _ => doNothing 
} 

提取器實際上並不需要返回已經存在於其參數中的信息,這只是常見的用例。實際上,您可以使用任何部分函數,​​將其提升到Option,並且可以匹配關於函數是否已定義及其值的信息。

+0

嗯。有趣的方法。我知道這種技術,但不相信我可以在這種情況下使用它。我會考慮它對我的情況是否有用。 – Debilski 2010-08-20 16:10:38

+0

當然,當我不僅要使用'req'還要使用'id'(例如req @ Req(\'path \':: id :: Nil,_,_)''' ...我們只希望這不會發生。 – Debilski 2010-08-20 16:13:40

+0

實際上應該沒問題。沒有理由不能使用@ -patterns或將參數內的新變量綁定到提取器。 – 2010-08-20 17:19:32

1

將if表達式重構爲case語句內容有什麼問題?

Req("a"::"b"::Nil, 3) match { 
    case [email protected](`path` :: _ :: Nil, 3) => 
    val res=findInDb(r) 
    if(res.isDefined) doSomethingWith(res) 
    else doDefault 
    case [email protected](`path` :: _ :: Nil, _) => doDefault 
    case _ => doNothing 
} 
+0

因爲那麼它不會再嘗試與其他語句匹配。而且我無法重構它,因爲這些是部分函數,​​並且在代碼中的其他地方。所以,一切都必須在匹配的聲明中完成。 – Debilski 2010-08-20 18:02:01

1

您可以創建的基礎設施一點點,讓你在不易受攻擊的方式換一個變種:

class Memory[M] { 
    // Could throw exceptions or whatnot if you tried to assign twice 
    private[this] var mem: Option[M] = None 
    def apply(om: Option[M]) = { mem = om; mem } 
    def apply(m: M) = { mem = Some(m); mem } 
    def apply() = { mem } 
} 

// Need to create an object that memorizes the correct type 
object MemorizeInt { 
    def unapply[A](a: A) = Some((a,new Memory[Int])) 
} 

case class Req(path: List[String], requestType: Int) 
def findInDb(req: Req) = if(req.path.length > 0) Some(2) else None 
def doSomethingWith(oi: Option[Int]) { 
    println(oi) 
} 

Req("a"::"b"::Nil, 3) match { 
    case MemorizeInt([email protected](path :: _ :: Nil, 3),m) if m(findInDb(r)).isDefined => 
    doSomethingWith(m()) 
    case [email protected](path :: _ :: Nil, _) => {} 
    case _ => {} 
} 

或者,你可以從=>後,使用工作進入條件map

case class Req(path: List[String], requestType: Int) 
def findInDb(req: Req) = if(req.path.length > 0) Some(2) else None 
def doSomethingWith(i: Int) { println(i) } 

Req("a"::"b"::Nil, 3) match { 
    case [email protected](path :: _ :: Nil, 3) if 
    findInDb(r).map(m => { doSomethingWith(m); m }).isDefined => {} 
    case [email protected](path :: _ :: Nil, _) => println("default") 
    case _ => println("messed up") 
} 
3

試試這個:

object FindInDB { 
    def unapply(req:Req) => { 
    //Your sophisticated logic 
    if(req.path.length > 3) Some((req, 2)) else None 
    } 

然後在你的case語句,你可以這樣做:

Req("a"::"b"::Nil, 3) match { 
    case FindInDb(r, n) => //Now you can see both the Req, and the Int 
    ...