Объем переменных внутри оператора case scala - PullRequest
8 голосов
/ 20 августа 2010

Для разработки лифта мне иногда нужно использовать match - case операторы, подобные следующим.(Переписано на простой scala для более легкого понимания.) Одно примечание к ним: На самом деле это разные частичные функции, определенные в разных частях кода, поэтому важно, чтобы инструкция case не выполнялась в или перед защитником, чтобы иметь другую частичную частьоцениваемые функции (если сопоставление не удается, то есть).

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

Теперь, чтобы узнать, что оператор case выполнен успешно, я должен запросить базу данных с помощью findInDb и проверить, верен ли результат,После этого я должен вызвать его снова, чтобы использовать значение.

Выполнение чего-то вроде

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

не работает, поскольку область действия res ограничивается внутренними скобками.

Конечно, я могу определить var res = _ снаружи и назначить его, но я не очень хорошо себя чувствую, делая это.

Можно ли каким-либо образом объявить переменную внутри сторожа?Если возможно сделать case r@Req(…), почему бы не case r@Req() if res@(r.isDefined)?

Ответы [ 5 ]

9 голосов
/ 20 августа 2010

Ты на самом деле очень близко. Ключевым элементом, который отсутствует, является использование экстрактора, а не защитного выражения.

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
}

Экстракторы на самом деле не обязаны возвращать только информацию, уже присутствующую в их аргументах, это всего лишь общий случай использования. Вы можете фактически использовать любую частичную функцию, поднять ее в Option и иметь возможность сопоставлять информацию о том, определена ли функция и ее значение.

3 голосов
/ 04 апреля 2013

попробуйте это:

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

тогда в вашем случае вы можете сделать:

Req("a"::"b"::Nil, 3) match {
  case FindInDb(r, n) => //Now you can see both the Req, and the Int
  ...
1 голос
/ 20 августа 2010

Вы можете создать небольшую инфраструктуру, которая позволит вам оборачивать переменную менее уязвимым образом:

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(r@Req(path :: _ :: Nil, 3),m) if m(findInDb(r)).isDefined =>
    doSomethingWith(m())
  case r@Req(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 r@Req(path :: _ :: Nil, 3) if 
    findInDb(r).map(m => { doSomethingWith(m); m }).isDefined => {}
  case r@Req(path :: _ :: Nil, _) => println("default")
  case _ => println("messed up")
}
1 голос
/ 20 августа 2010

Что не так с рефакторингом выражения if внутри оператора case?

Req("a"::"b"::Nil, 3) match {
  case r@Req(`path` :: _ :: Nil, 3) =>
    val res=findInDb(r)
    if(res.isDefined) doSomethingWith(res)
    else doDefault
  case r@Req(`path` :: _ :: Nil, _) => doDefault
  case _ => doNothing
}
0 голосов
/ 20 августа 2010

Вы пробовали case r @ Req() if res@(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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...