8

对于电梯开发,我有时需要使用match-<code>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)呢?

4

5 回答 5

9

你实际上非常接近。缺少的关键部分是使用提取器而不是保护表达式。

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,并且能够匹配有关函数是否已定义及其值的信息。

于 2010-08-20T16:05:17.870 回答
3

尝试这个:

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
  ...
于 2013-04-04T13:40:36.870 回答
1

您可以创建一些基础设施,让您以较少暴露的方式包装 var:

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")
}
于 2010-08-20T18:50:22.163 回答
1

将 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
}
于 2010-08-20T17:37:28.073 回答
0

你试过了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
于 2010-08-20T15:58:51.910 回答