2010-08-20 9 views
8

Para el desarrollo de la elevación, a veces necesito usar las declaraciones match - case como las siguientes. (Reescrito a escala simple para una comprensión más fácil.) Una nota para ellos: en realidad son funciones parciales diferentes, definidas en diferentes partes del código, por lo que es importante que una declaración de caso falle en o antes del guardia para que el otro parcial funciones evaluadas (si la coincidencia falla, eso es).Alcance de las variables dentro de la declaración de protección de la caja de scala

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

Ahora, con el fin de saber que la declaración case tiene éxito, hay que consultar la base de datos con findInDb y comprobar si el resultado es válido. Después, debo volver a llamar para usar el valor.

Hacer algo como

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

no funciona debido a que el alcance de res se limita entonces a dentro de las llaves.

Definitivamente puedo definir un var res = _ fuera y asignarlo, pero no me siento bien al hacerlo.

¿Es de alguna manera posible declarar una variable dentro del protector? Si es posible hacer case [email protected](…) ¿por qué no case [email protected]() if [email protected](r.isDefined)?

Respuesta

0

¿Has probado 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

yo no entiendo muy bien su respuesta. Cuando escribí (o al menos tenía la esperanza de que la gente creyera que realmente lo intenté), traté de usar 'if res @ (algo)' y no es una sintaxis válida. – Debilski

9

De hecho, estás muy cerca. La pieza clave que falta es usar un extractor en lugar de una expresión de 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 
} 

Los extractores no están obligados a devolver solo la información ya presente en sus argumentos, ese es el caso de uso común. En realidad, puede usar cualquier función parcial, levantarla a Opción y poder hacer coincidir la información sobre si la función está definida y su valor.

+0

Hmm. Enfoque interesante. Conocía la técnica pero no creía que pudiera usarla en ese caso. Pensaré si es útil en mi caso. – Debilski

+0

Se pone bastante desordenado, por supuesto, cuando quiero usar no solo 'req' sino también' id' como en 'case req @ Req (\' ruta \ ':: id :: Nil, _, _)' ... Esperemos que esto no suceda. – Debilski

+0

Debería estar bien, en realidad. No hay razón por la que no puedas usar @patterns o vincular nuevas variables dentro de los argumentos a un extractor. –

1

¿Qué hay de malo en refactorizar la expresión if para que esté dentro de la sentencia 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

Porque entonces no intentará hacer coincidir las otras declaraciones más. Y no puedo refactorizarlo, porque estas son funciones parciales y en otro lugar del código. Entonces, todo tiene que hacerse en la declaración correspondiente. – Debilski

1

Se puede crear un poco de la infraestructura para que pueda envolver una var de una manera menos expuesto:

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

Como alternativa, puede mover el trabajo de después de la => en el condicional mediante el uso de 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

intente esto:

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

continuación, en su declaración de caso que puede hacer:

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