首页 > 解决方案 > 避免在模式匹配情况下重做不必要的计算

问题描述

这个问题是 Scala 的模式匹配语法特有的。因此,假设我有一些与此等效的代码:

def process(seq: Seq[SomeObjectType]): SomeReturnType = seq match {
    case Seq() => // Return something
    case s if s exists (o => o.somePropertyTest) => {
        // Ok, the test is satisfied, now let's find
        // that object in the sequence that satisfies
        // it and do something with it
    }
    case _ => // Return something if no other case matches
}

现在很明显,这段代码的效率并不高,因为我检查序列中是否有一个元素满足一些测试,然后在这种情况下,我继续找到元素并使用它,这可以做一些像这样避免遍历序列两次:

def process(seq: Seq[SomeObjectType]): SomeReturnType = seq match {
    case Seq() => // Return something
    case s => {
        val obj = s find (o => o.somePropertyTest)
        if !obj.isEmpty {
            // Ok, the test is satisfied, and we have
            // the object that satisfies it, obj, so
            // do something with it directly
        } else {
            // Handle the no-match case here instead
        }
    }
}

但这违背了模式匹配的全部目的,尤其是在我进行测试之后有多种情况时。如果测试不通过,我仍然希望执行落到下一个案例,即如果没有元素满足条件,但我也想“保留”在测试中找到的序列的元素并直接使用它案例主体,有点类似于人们可以用来@为复杂案例命名并在其主体中使用该名称的方式。我正在尝试通过模式匹配做的事情吗?还是模式匹配太复杂而无法处理?

请注意,为清楚起见,这是一个简化的示例。在我的实际问题中,序列很长,并且对序列的每个元素的测试包括构建一个相对较大的树并检查该树是否具有某些属性,因此仅处理低效的第一个版本确实不是一个选择.

标签: scalapattern-matching

解决方案


您可以使用方法定义自定义提取器对象unapply。限制是您不能在模式匹配中内联创建这样的对象。您必须在匹配之前显式创建它。例如:

case class Selector[T](condition: T => Boolean) {
  def unapply(seq: Seq[T]): Option[T] = seq.find(condition)
}
object Selector {
  // seq argument is used only to infer type `T`
  def from[T](seq: Seq[T])(condition: T => Boolean): Selector[T] = Selector(condition)
}

def process(seq: Seq[SomeObjectType]): SomeReturnType = {
  // Create an extractor object instance
  val Select = Selector.from(seq)(_.somePropertyTest)

  seq match {
    case Seq() =>     // seq empty
    case Select(o) => // found o, we can process it now
    case _ =>         // didn't find a suitable element
  }
}  

推荐阅读