Scala 避免在模式匹配情况下重复不必要的计算
这个问题在某种程度上与Scala的模式匹配语法有关。假设我有一些代码与此等价:Scala 避免在模式匹配情况下重复不必要的计算,scala,pattern-matching,Scala,Pattern Matching,这个问题在某种程度上与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
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
}
}
}
但这违背了模式匹配的全部目的,特别是在我进行测试的一个案例之后有多个案例的情况下。如果测试没有通过,我仍然希望执行落到下一个案例中,即如果没有元素满足条件,但我还希望“保留”在测试中找到的序列元素,并直接在案例体中使用它,有点类似于人们可以使用@
为复杂的案例命名并在其主体中使用该名称的方式。我所尝试的模式匹配是否可行?还是模式匹配太复杂而无法处理
请注意,为了清晰起见,这是一个简化的示例。在我的实际问题中,序列很长,对序列的每个元素的测试包括构建一个相对较大的树,并检查该树的某些属性是否有效,因此仅处理效率低下的第一个版本实际上不是一个选项。未测试,而是类似以下内容:
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是一种递归结构,进程在其上递归。您可以使用方法定义自定义。限制是您不能在模式匹配内部内联创建这样的对象。您必须在匹配之前显式地创建它。例如:
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
}
}
我一直希望Scala具有将任意表达式视为提取器的语法。想象一下,如果您可以编写seq match{case{{.find({.prop)}(o)=>…}
。它有很好的语义,而且很明显如何去修饰它;只是没有语法。在我看来,当函数式语言对待变量的方式不同于它们所代表的表达式时,这是一个明显的语法鸿沟。我同意!这将大大提高Scala模式匹配的能力和灵活性,这是该语言的核心功能之一。当然,这是将模式匹配和递归调用结合起来遍历序列和处理每个元素的标准方法。但是你假设我的“进程”函数是递归的,而事实并非如此。所以这并不能回答我的问题。为什么你的过程函数不能是递归的?我同意,我不清楚为什么我特别不希望它是递归的。我对斯劳克答案的评论希望能让它更清楚。哇,这真的很有效!我喜欢这个解决方案的地方在于,它将大部分脏活移到了模式匹配本身之外。我仍然不确定我是否完全理解它,尤其是seq
如何匹配选择(o)
?在本例中,后者不是类型为选择器[SomeObjectType]
吗?或者这个提取器范例引入了一些魔法?@Namefie是的,某种魔法seq match{case Select(o)=>}
调用Select.unapply(seq)
,其结果是选项[SomeObjectType]
。如果此未应用
返回Some
,则匹配案例成功,结果SomeObjectType
被分配给o
。换句话说,它相当于Select.unapply(seq)match{case Some(o)=>}
Select
本身只是一个对象,它有一个不适用的方法,是Selector
类的实例,因此Select(o)
在case
之外是非法的,并且没有类型。