【问题标题】:Avoid redoing unnecessary computation in pattern matching case避免在模式匹配情况下重做不必要的计算
【发布时间】:2018-04-25 22:33:48
【问题描述】:

这个问题是 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
        }
    }
}

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

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

【问题讨论】:

  • 我一直希望 Scala 具有将任意表达式视为提取器的语法。想象一下,如果你能写seq match { case { _.find(_.prop) }(o) => ... }。它具有完美的语义意义,并​​且很明显如何对其进行脱糖;只是没有语法。当函数式语言以不同于它们所代表的表达式的方式处理变量时,IMO 是一个明显的句法差距。
  • 我同意!这将显着提高 Scala 模式匹配的功能和灵活性,这是该语言的核心特性之一。

标签: scala pattern-matching


【解决方案1】:

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

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

【讨论】:

  • 哇,这真的有效!我喜欢这个解决方案的地方在于它将大部分脏工作移到了模式匹配本身之外。我仍然不确定我是否完全理解它,特别是,seq 怎么能匹配Select(o)?在这种情况下,后者不是Selector[SomeObjectType] 类型吗?还是这种提取器范式引入了一些魔法?
  • @Namefie 是的,有点神奇。 seq match { case Select(o) => } 调用Select.unapply(seq),其结果为Option[SomeObjectType]。如果这个unapply 返回Some,则匹配成功,结果SomeObjectType 被分配给o。换句话说,它相当于Select.unapply(seq) match { case Some(o) => }。 Select 本身只是一个带有unapply 方法的对象,是Selector 类的一个实例,所以Select(o) 在case 之外是非法的,并且没有类型。
【解决方案2】:

未测试,但类似这样:

def process(seq: Seq[SomeObjectType]): SomeReturnType = seq match {
    case Seq() => // Return something
    case x +: xs if x.somePropertyTest == desiredProperty => something(x)
    case x +: xs => process(xs)
}

之所以有效,是因为 Seq 是一种递归结构,进程在该结构上递归。

【讨论】:

  • 好吧,这是结合模式匹配和递归调用来遍历序列和处理每个元素的标准方法。但是您假设我的“过程”函数是递归的,而事实并非如此。所以这根本不能回答我的问题。
  • 为什么你的流程函数不能递归?
  • 我同意不清楚为什么我特别不希望它是递归的。我对 slouc 的回答的评论希望能让它更清楚。
猜你喜欢
  • 1970-01-01
  • 2021-08-12
  • 2020-10-21
  • 1970-01-01
  • 1970-01-01
  • 2011-10-14
  • 1970-01-01
  • 2014-01-16
  • 2015-09-08
相关资源
最近更新 更多