Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope of variables inside scala’s case guard statement

For lift development, I sometimes need to use matchcase statements like the following. (Rewritten to plain scala for easier understanding.) One note to them: These are actually different partial functions, defined in different parts of the code, so it’s important that a case statement fails in or before the guard in order to have the other partial functions evaluated (if matching fails, that is).

// 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 r@Req(`path` :: _ :: Nil, 3) if findInDb(r).isDefined =>
    doSomethingWith(findInDb(r))
  case r@Req(`path` :: _ :: Nil, _) => doDefault
  case _ => doNothing
}

Now, in order to know that the case statement succeeds, I must query the database with findInDb and check whether the result is valid. Afterwards, I must call it again to use the value.

Doing something like

case r@Req(path, 3) if {val res = findInDb(r); res.isDefined} =>

does not work because the scope of res is then limited to inside the braces.

I can of course define a var res = _ outside and assign to it, but I don’t feel well doing this.

Is it by any means possible to declare a variable inside the guard? If it is possible to do case r@Req(…) why not case r@Req() if res@(r.isDefined)?

like image 756
Debilski Avatar asked Aug 20 '10 15:08

Debilski


2 Answers

You're actually very close. The key piece missing is to use an extractor rather than a guard expression.

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

Req("a"::"b"::Nil, 3) match {
  case dbResult@FindInDb(Req(`path` :: _ :: Nil, 3))=> doSomethingWith(dbResult)
  case Req(`path` :: _ :: Nil, _) => doDefault
  case _ => doNothing
}

Extractors aren't actually required to return only information already present in their arguments, that's just the common use case. You can actually use any partial function, lift it to Option, and be able to match on information about both whether the function is defined and its value.

like image 149
Dave Griffith Avatar answered Sep 21 '22 03:09

Dave Griffith


try this:

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

then in your case statement you can do:

Req("a"::"b"::Nil, 3) match {
  case FindInDb(r, n) => //Now you can see both the Req, and the Int
  ...
like image 22
Rodolfo Hansen Avatar answered Sep 19 '22 03:09

Rodolfo Hansen