2010-08-20 15 views
8

Per lo sviluppo degli ascensori, a volte ho bisogno di utilizzare le affermazioni match - case come le seguenti. (Riscritto a scala semplice per facilitare la comprensione.) Una nota per loro: Queste sono in realtà diverse funzioni parziali, definite in diverse parti del codice, quindi è importante che una dichiarazione del caso fallisca nella o prima della guardia per avere l'altra parziale funzioni valutate (se la corrispondenza fallisce, cioè).Ambito delle variabili all'interno della guardia di scala dello statement

// 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 
} 

Ora, al fine di sapere che la dichiarazione case riesce, devo interrogare il database con findInDb e controllare se il risultato è valido. Successivamente, devo chiamarlo di nuovo per usare il valore.

fare qualcosa di simile

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

non funziona perché la portata della res è quindi limitato a all'interno delle parentesi graffe.

Posso ovviamente definire un var res = _ esterno e assegnarlo ad esso, ma non mi sento bene a farlo.

E 'possibile dichiarare una variabile all'interno della guardia? Se è possibile fare case [email protected](…) perché non case [email protected]() if [email protected](r.isDefined)?

risposta

0

Hai provato 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

io non capisco la vostra risposta. Come ho scritto (o almeno speravo che la gente credesse che l'ho provato), ho provato a usare "if res @ (something)" e non è una sintassi valida. – Debilski

9

In realtà sei molto vicino. Il pezzo chiave mancante è usare un estrattore piuttosto che un'espressione di guardia.

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 
} 

Estrattori non esiste alcun obbligo di restituire solo le informazioni già presenti nelle loro argomentazioni, questo è solo il caso d'uso comune. È possibile utilizzare qualsiasi funzione parziale, sollevarla in Option e essere in grado di far corrispondere informazioni sia sulla definizione della funzione che sul suo valore.

+0

Hmm. Approccio interessante Conoscevo la tecnica ma non credevo di poterla usare in quel caso. Penserò se è utile nel mio caso. – Debilski

+0

Ovviamente è un po 'incasinato quando voglio usare non solo 'req', ma anche' id' come in 'case req @ Req (\' path \ ':: id :: Nil, _, _)' ... Speriamo solo che questo non accada. – Debilski

+0

Dovrebbe andare bene, in realtà. Non è possibile utilizzare @ -patterns o associare nuove variabili all'interno degli argomenti ad un estrattore. –

1

Cosa c'è di sbagliato nel refactoring dell'espressione if all'interno della dichiarazione del caso?

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

Perché non tenterà più di abbinare le altre istruzioni. E non posso refactoring, perché queste sono funzioni parziali e da qualche altra parte nel codice. Quindi, tutto deve essere fatto nella dichiarazione corrispondente. – Debilski

1

Si potrebbe creare un po 'di infrastrutture per farvi avvolgere un var in un modo meno esposta:

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 _ => {} 
} 

In alternativa, è possibile spostare il lavoro da dopo la => nella condizionale utilizzando 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

provare questo:

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

quindi nel tuo caso affermazione si può fare:

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