Для разработки лифта мне иногда нужно использовать операторы 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 case
успешно выполняется, я должен запросить базу данных с помощью findInDb
и проверить, является ли результат действует. После этого я должен вызвать его снова, чтобы использовать значение.
Выполнение чего-то вроде
case r@Req(path, 3) if {val res = findInDb(r); res.isDefined} =>
не работает, поскольку область действия res
затем ограничивается внутренними скобками.
Конечно, я могу определить var res = _
снаружи и присвоить ему, но мне не очень хорошо это делать.
Можно ли каким-либо образом объявить переменную внутри охранник? Если это возможно сделать случайr @ Req (…)
почему бы не case r @ Req (), если res @(r.isDefined)
?
Что не так с рефакторингом выражения 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
}
На самом деле вы очень близки. Ключевой элемент, которого не хватает, - использовать экстрактор, а не охранное выражение.
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 и иметь возможность сопоставлять информацию как о том, определена ли функция, так и о ее значении.
Вы можете создать небольшую инфраструктуру, которая позволит вам обернуть переменную менее открытым способом:
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")
}