1
votes

This problem is kind of specific to Scala's pattern matching syntax. So let's say I have some code equivalent to this:

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
}

Now obviously, this code is not as efficient as it could be since I check if there is an element in the sequence that satisfies some test, then in that case I go ahead and find the element and work with it, which can be done somewhat like this to avoid traversing the sequence twice:

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

But this defeats the whole purpose of pattern matching, especially when there are multiple cases after the one where I do the test. I still want the execution to fall to the next case if the test does not pass, i.e. if no element satisfies the condition, but I also want to "retain" the element of the sequence that was found in the test and use it directly in the case body, kind of similar to the way one can use @ to give a name to a complex case and use that name in its body. Is what I'm trying to do possible with pattern matching? Or is it just too complicated for pattern matching to be able to handle it?

Note that this is a simplified example for clarity. In my actual problem, the sequences are long and the test on each element of a sequence consists in building a relatively big tree and checking whether some properties hold for that tree, so it is really not an option to just do with the inefficient first version.

2
I have long wished that Scala had syntax to treat an arbitrary expression as an extractor. Imagine if you could write seq match { case { _.find(_.prop) }(o) => ... }. It makes perfect semantic sense and it's obvious how to desugar it; there's just no syntax for it. IMO it is an obvious syntactic gap when a functional language treats variables differently than the expressions they stand for. - Owen
I agree! This would significantly increase the power and flexibility of Scala's pattern matching, which is one the central features of the language. - Namefie

2 Answers

2
votes

You can define a custom extractor object with an unapply method. The limitation is that you can't create such an object inline, inside the pattern match. You have to create it explicitly before matching. For example:

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

Not tested, but something like this:

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

This works because a Seq is a recursive structure, over which process recurses.